mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-02-28 13:39:40 +00:00
More refactoring / cleanup. Code policies.
This commit is contained in:
parent
60ffaea195
commit
91de6392ca
36 changed files with 3877 additions and 3927 deletions
|
@ -120,6 +120,10 @@ else ()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if(BUILD_VR_OPENXR)
|
if(BUILD_VR_OPENXR)
|
||||||
|
# TODO: Move this into something akin to add_openmw_dir instead of breaking pattern.
|
||||||
|
# Later, openmw and openmw_vr should preferrably share game code as a static or shared library
|
||||||
|
# instead of being compiled separately, though for now that's not possible as i depend on
|
||||||
|
# USE_OPENXR preprocessor switches.
|
||||||
set(OPENMW_VR_FILES
|
set(OPENMW_VR_FILES
|
||||||
vrengine.cpp
|
vrengine.cpp
|
||||||
mwvr/openxraction.hpp
|
mwvr/openxraction.hpp
|
||||||
|
@ -165,7 +169,7 @@ if(BUILD_VR_OPENXR)
|
||||||
${APPLE_BUNDLE_RESOURCES}
|
${APPLE_BUNDLE_RESOURCES}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Preprocessor variable used to control code paths between the vr port and non-vr.
|
# Preprocessor variable used to control code paths to vr code
|
||||||
target_compile_options(openmw_vr PUBLIC -DUSE_OPENXR -DXR_USE_GRAPHICS_API_OPENGL -DXR_USE_PLATFORM_WIN32)
|
target_compile_options(openmw_vr PUBLIC -DUSE_OPENXR -DXR_USE_GRAPHICS_API_OPENGL -DXR_USE_PLATFORM_WIN32)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -555,7 +555,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||||
|
|
||||||
MWInput::InputManager* input =
|
MWInput::InputManager* input =
|
||||||
#ifdef USE_OPENXR
|
#ifdef USE_OPENXR
|
||||||
new MWVR::OpenXRInputManager(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
new MWVR::VRInputManager(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
||||||
#else
|
#else
|
||||||
new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1076,10 +1076,6 @@ namespace MWRender
|
||||||
// TODO: It's difficult to design a good override system when
|
// TODO: It's difficult to design a good override system when
|
||||||
// I don't have a good understanding of the animation code. So for
|
// I don't have a good understanding of the animation code. So for
|
||||||
// now i just hardcode blocking of updaters for nodes that should not be animated in VR.
|
// now i just hardcode blocking of updaters for nodes that should not be animated in VR.
|
||||||
|
|
||||||
// TODO: Some overrides cause NaN during cull.
|
|
||||||
// I aassume this happens if an override causes a bone to never receive a valid matrix
|
|
||||||
|
|
||||||
// Add any bone+groupname pair that is messing with Vr comfort here.
|
// Add any bone+groupname pair that is messing with Vr comfort here.
|
||||||
using Overrides = std::set<std::string>;
|
using Overrides = std::set<std::string>;
|
||||||
using GroupOverrides = std::map<std::string, Overrides>;
|
using GroupOverrides = std::map<std::string, Overrides>;
|
||||||
|
|
|
@ -5,83 +5,83 @@
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
|
||||||
OpenXRAction::OpenXRAction(
|
OpenXRAction::OpenXRAction(
|
||||||
XrAction action,
|
XrAction action,
|
||||||
XrActionType actionType,
|
XrActionType actionType,
|
||||||
const std::string& actionName,
|
const std::string& actionName,
|
||||||
const std::string& localName)
|
const std::string& localName)
|
||||||
: mAction(action)
|
: mAction(action)
|
||||||
, mType(actionType)
|
, mType(actionType)
|
||||||
, mName(actionName)
|
, mName(actionName)
|
||||||
, mLocalName(localName)
|
, mLocalName(localName)
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
OpenXRAction::~OpenXRAction() {
|
|
||||||
if (mAction)
|
|
||||||
{
|
{
|
||||||
xrDestroyAction(mAction);
|
};
|
||||||
|
|
||||||
|
OpenXRAction::~OpenXRAction() {
|
||||||
|
if (mAction)
|
||||||
|
{
|
||||||
|
xrDestroyAction(mAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRAction::getFloat(XrPath subactionPath, float& value)
|
||||||
|
{
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
|
||||||
|
getInfo.action = mAction;
|
||||||
|
getInfo.subactionPath = subactionPath;
|
||||||
|
|
||||||
|
XrActionStateFloat xrValue{ XR_TYPE_ACTION_STATE_FLOAT };
|
||||||
|
CHECK_XRCMD(xrGetActionStateFloat(xr->impl().xrSession(), &getInfo, &xrValue));
|
||||||
|
|
||||||
|
if (xrValue.isActive)
|
||||||
|
value = xrValue.currentState;
|
||||||
|
return xrValue.isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRAction::getBool(XrPath subactionPath, bool& value)
|
||||||
|
{
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
|
||||||
|
getInfo.action = mAction;
|
||||||
|
getInfo.subactionPath = subactionPath;
|
||||||
|
|
||||||
|
XrActionStateBoolean xrValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
|
||||||
|
CHECK_XRCMD(xrGetActionStateBoolean(xr->impl().xrSession(), &getInfo, &xrValue));
|
||||||
|
|
||||||
|
if (xrValue.isActive)
|
||||||
|
value = xrValue.currentState;
|
||||||
|
return xrValue.isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pose action only checks if the pose is active or not
|
||||||
|
bool OpenXRAction::getPoseIsActive(XrPath subactionPath)
|
||||||
|
{
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
|
||||||
|
getInfo.action = mAction;
|
||||||
|
getInfo.subactionPath = subactionPath;
|
||||||
|
|
||||||
|
XrActionStatePose xrValue{ XR_TYPE_ACTION_STATE_POSE };
|
||||||
|
CHECK_XRCMD(xrGetActionStatePose(xr->impl().xrSession(), &getInfo, &xrValue));
|
||||||
|
|
||||||
|
return xrValue.isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRAction::applyHaptics(XrPath subactionPath, float amplitude)
|
||||||
|
{
|
||||||
|
amplitude = std::max(0.f, std::min(1.f, amplitude));
|
||||||
|
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
XrHapticVibration vibration{ XR_TYPE_HAPTIC_VIBRATION };
|
||||||
|
vibration.amplitude = amplitude;
|
||||||
|
vibration.duration = XR_MIN_HAPTIC_DURATION;
|
||||||
|
vibration.frequency = XR_FREQUENCY_UNSPECIFIED;
|
||||||
|
|
||||||
|
XrHapticActionInfo hapticActionInfo{ XR_TYPE_HAPTIC_ACTION_INFO };
|
||||||
|
hapticActionInfo.action = mAction;
|
||||||
|
hapticActionInfo.subactionPath = subactionPath;
|
||||||
|
CHECK_XRCMD(xrApplyHapticFeedback(xr->impl().xrSession(), &hapticActionInfo, (XrHapticBaseHeader*)&vibration));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenXRAction::getFloat(XrPath subactionPath, float& value)
|
|
||||||
{
|
|
||||||
auto* xr = Environment::get().getManager();
|
|
||||||
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
|
|
||||||
getInfo.action = mAction;
|
|
||||||
getInfo.subactionPath = subactionPath;
|
|
||||||
|
|
||||||
XrActionStateFloat xrValue{ XR_TYPE_ACTION_STATE_FLOAT };
|
|
||||||
CHECK_XRCMD(xrGetActionStateFloat(xr->impl().xrSession(), &getInfo, &xrValue));
|
|
||||||
|
|
||||||
if (xrValue.isActive)
|
|
||||||
value = xrValue.currentState;
|
|
||||||
return xrValue.isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenXRAction::getBool(XrPath subactionPath, bool& value)
|
|
||||||
{
|
|
||||||
auto* xr = Environment::get().getManager();
|
|
||||||
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
|
|
||||||
getInfo.action = mAction;
|
|
||||||
getInfo.subactionPath = subactionPath;
|
|
||||||
|
|
||||||
XrActionStateBoolean xrValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
|
|
||||||
CHECK_XRCMD(xrGetActionStateBoolean(xr->impl().xrSession(), &getInfo, &xrValue));
|
|
||||||
|
|
||||||
if (xrValue.isActive)
|
|
||||||
value = xrValue.currentState;
|
|
||||||
return xrValue.isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pose action only checks if the pose is active or not
|
|
||||||
bool OpenXRAction::getPoseIsActive(XrPath subactionPath)
|
|
||||||
{
|
|
||||||
auto* xr = Environment::get().getManager();
|
|
||||||
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
|
|
||||||
getInfo.action = mAction;
|
|
||||||
getInfo.subactionPath = subactionPath;
|
|
||||||
|
|
||||||
XrActionStatePose xrValue{ XR_TYPE_ACTION_STATE_POSE };
|
|
||||||
CHECK_XRCMD(xrGetActionStatePose(xr->impl().xrSession(), &getInfo, &xrValue));
|
|
||||||
|
|
||||||
return xrValue.isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenXRAction::applyHaptics(XrPath subactionPath, float amplitude)
|
|
||||||
{
|
|
||||||
amplitude = std::max(0.f, std::min(1.f, amplitude));
|
|
||||||
|
|
||||||
auto* xr = Environment::get().getManager();
|
|
||||||
XrHapticVibration vibration{ XR_TYPE_HAPTIC_VIBRATION };
|
|
||||||
vibration.amplitude = amplitude;
|
|
||||||
vibration.duration = XR_MIN_HAPTIC_DURATION;
|
|
||||||
vibration.frequency = XR_FREQUENCY_UNSPECIFIED;
|
|
||||||
|
|
||||||
XrHapticActionInfo hapticActionInfo{ XR_TYPE_HAPTIC_ACTION_INFO };
|
|
||||||
hapticActionInfo.action = mAction;
|
|
||||||
hapticActionInfo.subactionPath = subactionPath;
|
|
||||||
CHECK_XRCMD(xrApplyHapticFeedback(xr->impl().xrSession(), &hapticActionInfo, (XrHapticBaseHeader*)&vibration));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
/// \brief C++ wrapper for the XrAction type
|
||||||
struct OpenXRAction
|
struct OpenXRAction
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -12,289 +12,288 @@
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
|
||||||
OpenXRInput::OpenXRInput(const std::vector<SuggestedBindings>& suggestedBindings)
|
OpenXRInput::OpenXRInput(const std::vector<SuggestedBindings>& suggestedBindings)
|
||||||
: mActionSet(createActionSet())
|
: mActionSet(createActionSet())
|
||||||
{
|
|
||||||
// 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<ButtonPressAction>(MWInput::A_GameMenu, "game_menu", "Game Menu");
|
|
||||||
createMWAction<ButtonLongPressAction>(A_Recenter, "reposition_menu", "Reposition Menu");
|
|
||||||
createMWAction<ButtonPressAction>(MWInput::A_Inventory, "inventory", "Inventory");
|
|
||||||
createMWAction<ButtonPressAction>(MWInput::A_Activate, "activate", "Activate");
|
|
||||||
createMWAction<ButtonHoldAction>(MWInput::A_Use, "use", "Use");
|
|
||||||
createMWAction<ButtonPressAction>(MWInput::A_Jump, "jump", "Jump");
|
|
||||||
createMWAction<ButtonPressAction>(MWInput::A_ToggleWeapon, "weapon", "Weapon");
|
|
||||||
createMWAction<ButtonPressAction>(MWInput::A_ToggleSpell, "spell", "Spell");
|
|
||||||
createMWAction<ButtonPressAction>(MWInput::A_CycleSpellLeft, "cycle_spell_left", "Cycle Spell Left");
|
|
||||||
createMWAction<ButtonPressAction>(MWInput::A_CycleSpellRight, "cycle_spell_right", "Cycle Spell Right");
|
|
||||||
createMWAction<ButtonPressAction>(MWInput::A_CycleWeaponLeft, "cycle_weapon_left", "Cycle Weapon Left");
|
|
||||||
createMWAction<ButtonPressAction>(MWInput::A_CycleWeaponRight, "cycle_weapon_right", "Cycle Weapon Right");
|
|
||||||
createMWAction<ButtonHoldAction>(MWInput::A_Sneak, "sneak", "Sneak");
|
|
||||||
createMWAction<ButtonPressAction>(MWInput::A_QuickMenu, "quick_menu", "Quick Menu");
|
|
||||||
createMWAction<AxisAction>(MWInput::A_LookLeftRight, "look_left_right", "Look Left Right");
|
|
||||||
createMWAction<AxisAction>(MWInput::A_MoveForwardBackward, "move_forward_backward", "Move Forward Backward");
|
|
||||||
createMWAction<AxisAction>(MWInput::A_MoveLeftRight, "move_left_right", "Move Left Right");
|
|
||||||
createMWAction<ButtonLongPressAction>(MWInput::A_Journal, "journal_book", "Journal Book");
|
|
||||||
createMWAction<ButtonLongPressAction>(MWInput::A_QuickSave, "quick_save", "Quick Save");
|
|
||||||
createMWAction<ButtonPressAction>(MWInput::A_Rest, "rest", "Rest");
|
|
||||||
createMWAction<AxisAction>(A_ActivateTouch, "activate_touched", "Activate Touch");
|
|
||||||
createMWAction<ButtonPressAction>(MWInput::A_AlwaysRun, "always_run", "Always Run");
|
|
||||||
createMWAction<ButtonPressAction>(MWInput::A_AutoMove, "auto_move", "Auto Move");
|
|
||||||
createMWAction<ButtonLongPressAction>(MWInput::A_ToggleHUD, "toggle_hud", "Toggle HUD");
|
|
||||||
createMWAction<ButtonLongPressAction>(MWInput::A_ToggleDebug, "toggle_debug", "Toggle DEBUG");
|
|
||||||
createMWAction<AxisAction>(A_MenuUpDown, "menu_up_down", "Menu Up Down");
|
|
||||||
createMWAction<AxisAction>(A_MenuLeftRight, "menu_left_right", "Menu Left Right");
|
|
||||||
createMWAction<ButtonPressAction>(A_MenuSelect, "menu_select", "Menu Select");
|
|
||||||
createMWAction<ButtonPressAction>(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));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void
|
|
||||||
OpenXRInput::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
|
|
||||||
OpenXRInput::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<typename A, XrActionType AT>
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
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<XrActionSuggestedBinding> suggestedBindings =
|
|
||||||
{
|
{
|
||||||
{*mTrackerMap[TrackedLimb::LEFT_HAND], getXrPath(ActionPath::Pose, Side::LEFT_SIDE)},
|
// When starting to account for more devices than oculus touch, this section may need some expansion/redesign.
|
||||||
{*mTrackerMap[TrackedLimb::RIGHT_HAND], getXrPath(ActionPath::Pose, Side::RIGHT_SIDE)},
|
|
||||||
{*mHapticsMap[TrackedLimb::LEFT_HAND], getXrPath(ActionPath::Haptic, Side::LEFT_SIDE)},
|
// Currently the set of action paths was determined using the oculus touch (i know nothing about the vive and the index).
|
||||||
{*mHapticsMap[TrackedLimb::RIGHT_HAND], getXrPath(ActionPath::Haptic, Side::RIGHT_SIDE)},
|
// 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<ButtonPressAction>(MWInput::A_GameMenu, "game_menu", "Game Menu");
|
||||||
|
createMWAction<ButtonLongPressAction>(A_Recenter, "reposition_menu", "Reposition Menu");
|
||||||
|
createMWAction<ButtonPressAction>(MWInput::A_Inventory, "inventory", "Inventory");
|
||||||
|
createMWAction<ButtonPressAction>(MWInput::A_Activate, "activate", "Activate");
|
||||||
|
createMWAction<ButtonHoldAction>(MWInput::A_Use, "use", "Use");
|
||||||
|
createMWAction<ButtonPressAction>(MWInput::A_Jump, "jump", "Jump");
|
||||||
|
createMWAction<ButtonPressAction>(MWInput::A_ToggleWeapon, "weapon", "Weapon");
|
||||||
|
createMWAction<ButtonPressAction>(MWInput::A_ToggleSpell, "spell", "Spell");
|
||||||
|
createMWAction<ButtonPressAction>(MWInput::A_CycleSpellLeft, "cycle_spell_left", "Cycle Spell Left");
|
||||||
|
createMWAction<ButtonPressAction>(MWInput::A_CycleSpellRight, "cycle_spell_right", "Cycle Spell Right");
|
||||||
|
createMWAction<ButtonPressAction>(MWInput::A_CycleWeaponLeft, "cycle_weapon_left", "Cycle Weapon Left");
|
||||||
|
createMWAction<ButtonPressAction>(MWInput::A_CycleWeaponRight, "cycle_weapon_right", "Cycle Weapon Right");
|
||||||
|
createMWAction<ButtonHoldAction>(MWInput::A_Sneak, "sneak", "Sneak");
|
||||||
|
createMWAction<ButtonPressAction>(MWInput::A_QuickMenu, "quick_menu", "Quick Menu");
|
||||||
|
createMWAction<AxisAction>(MWInput::A_LookLeftRight, "look_left_right", "Look Left Right");
|
||||||
|
createMWAction<AxisAction>(MWInput::A_MoveForwardBackward, "move_forward_backward", "Move Forward Backward");
|
||||||
|
createMWAction<AxisAction>(MWInput::A_MoveLeftRight, "move_left_right", "Move Left Right");
|
||||||
|
createMWAction<ButtonLongPressAction>(MWInput::A_Journal, "journal_book", "Journal Book");
|
||||||
|
createMWAction<ButtonLongPressAction>(MWInput::A_QuickSave, "quick_save", "Quick Save");
|
||||||
|
createMWAction<ButtonPressAction>(MWInput::A_Rest, "rest", "Rest");
|
||||||
|
createMWAction<AxisAction>(A_ActivateTouch, "activate_touched", "Activate Touch");
|
||||||
|
createMWAction<ButtonPressAction>(MWInput::A_AlwaysRun, "always_run", "Always Run");
|
||||||
|
createMWAction<ButtonPressAction>(MWInput::A_AutoMove, "auto_move", "Auto Move");
|
||||||
|
createMWAction<ButtonLongPressAction>(MWInput::A_ToggleHUD, "toggle_hud", "Toggle HUD");
|
||||||
|
createMWAction<ButtonLongPressAction>(MWInput::A_ToggleDebug, "toggle_debug", "Toggle DEBUG");
|
||||||
|
createMWAction<AxisAction>(A_MenuUpDown, "menu_up_down", "Menu Up Down");
|
||||||
|
createMWAction<AxisAction>(A_MenuLeftRight, "menu_left_right", "Menu Left Right");
|
||||||
|
createMWAction<ButtonPressAction>(A_MenuSelect, "menu_select", "Menu Select");
|
||||||
|
createMWAction<ButtonPressAction>(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));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto& mwSuggestedBinding : mwSuggestedBindings.bindings)
|
void
|
||||||
|
OpenXRInput::createPoseAction(
|
||||||
|
TrackedLimb limb,
|
||||||
|
const std::string& actionName,
|
||||||
|
const std::string& localName)
|
||||||
{
|
{
|
||||||
auto xrAction = mActionMap.find(mwSuggestedBinding.action);
|
mTrackerMap.emplace(limb, new PoseAction(std::move(createXRAction(XR_ACTION_TYPE_POSE_INPUT, actionName, localName))));
|
||||||
if (xrAction == mActionMap.end())
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
OpenXRInput::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<typename A, XrActionType AT>
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
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<XrActionSuggestedBinding> suggestedBindings =
|
||||||
{
|
{
|
||||||
Log(Debug::Error) << "OpenXRInput: Unknown action " << mwSuggestedBinding.action;
|
{*mTrackerMap[TrackedLimb::LEFT_HAND], getXrPath(ActionPath::Pose, Side::LEFT_SIDE)},
|
||||||
continue;
|
{*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) });
|
||||||
}
|
}
|
||||||
suggestedBindings.push_back({ *xrAction->second, getXrPath(mwSuggestedBinding.path, mwSuggestedBinding.side) });
|
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
XrInteractionProfileSuggestedBinding xrSuggestedBindings{ XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
|
void
|
||||||
xrSuggestedBindings.interactionProfile = oculusTouchInteractionProfilePath;
|
OpenXRInput::generateControllerActionPaths(
|
||||||
xrSuggestedBindings.suggestedBindings = suggestedBindings.data();
|
ActionPath actionPath,
|
||||||
xrSuggestedBindings.countSuggestedBindings = (uint32_t)suggestedBindings.size();
|
const std::string& controllerAction)
|
||||||
CHECK_XRCMD(xrSuggestInteractionProfileBindings(xr->impl().xrInstance(), &xrSuggestedBindings));
|
|
||||||
}
|
|
||||||
|
|
||||||
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<OpenXRAction>
|
|
||||||
OpenXRInput::createXRAction(
|
|
||||||
XrActionType actionType,
|
|
||||||
const std::string& actionName,
|
|
||||||
const std::string& localName)
|
|
||||||
{
|
|
||||||
ActionPtr actionPtr = nullptr;
|
|
||||||
std::vector<XrPath> 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<OpenXRAction>{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;
|
auto* xr = Environment::get().getManager();
|
||||||
return Pose{};
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
it->second->update(time);
|
|
||||||
return it->second->value();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenXRInput::applyHaptics(TrackedLimb limb, float intensity)
|
std::unique_ptr<OpenXRAction>
|
||||||
{
|
OpenXRInput::createXRAction(
|
||||||
auto it = mHapticsMap.find(limb);
|
XrActionType actionType,
|
||||||
if (it == mHapticsMap.end())
|
const std::string& actionName,
|
||||||
|
const std::string& localName)
|
||||||
{
|
{
|
||||||
Log(Debug::Error) << "OpenXRInput: No such tracker: " << limb;
|
std::vector<XrPath> subactionPaths;
|
||||||
return;
|
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<OpenXRAction>{new OpenXRAction{ action, actionType, actionName, localName }};
|
||||||
}
|
}
|
||||||
|
|
||||||
it->second->apply(intensity);
|
void
|
||||||
}
|
OpenXRInput::updateControls()
|
||||||
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;
|
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];
|
||||||
}
|
}
|
||||||
return it->second[(int)side];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,61 +8,48 @@
|
||||||
|
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
/// \brief Generates and manages OpenXR Actions and ActionSets by generating openxr bindings from a list of SuggestedBindings structs.
|
||||||
struct SuggestedBindings
|
class OpenXRInput
|
||||||
{
|
|
||||||
struct Binding
|
|
||||||
{
|
{
|
||||||
int action;
|
public:
|
||||||
ActionPath path;
|
using Actions = MWInput::Actions;
|
||||||
Side side;
|
using ControllerActionPaths = std::array<XrPath, 2>;
|
||||||
|
|
||||||
|
OpenXRInput(const std::vector<SuggestedBindings>& suggestedBindings);
|
||||||
|
|
||||||
|
//! 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);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<typename A, XrActionType AT = A::ActionType>
|
||||||
|
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<OpenXRAction> 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<ActionPath, ControllerActionPaths> mPathMap;
|
||||||
|
std::map<int, std::unique_ptr<Action>> mActionMap;
|
||||||
|
std::map<TrackedLimb, std::unique_ptr<PoseAction>> mTrackerMap;
|
||||||
|
std::map<TrackedLimb, std::unique_ptr<HapticsAction>> mHapticsMap;
|
||||||
|
|
||||||
|
std::deque<const Action*> mActionQueue{};
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string controllerPath;
|
|
||||||
std::vector<Binding> bindings;
|
|
||||||
};
|
|
||||||
|
|
||||||
class OpenXRInput
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using Actions = MWInput::Actions;
|
|
||||||
using ControllerActionPaths = std::array<XrPath, 2>;
|
|
||||||
|
|
||||||
OpenXRInput(const std::vector<SuggestedBindings>& suggestedBindings);
|
|
||||||
|
|
||||||
//! 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);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
template<typename A, XrActionType AT = A::ActionType>
|
|
||||||
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<OpenXRAction> 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<ActionPath, ControllerActionPaths> mPathMap;
|
|
||||||
std::map<int, std::unique_ptr<Action>> mActionMap;
|
|
||||||
std::map<TrackedLimb, std::unique_ptr<PoseAction>> mTrackerMap;
|
|
||||||
std::map<TrackedLimb, std::unique_ptr<HapticsAction>> mHapticsMap;
|
|
||||||
|
|
||||||
std::deque<const Action*> mActionQueue{};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,24 +1,9 @@
|
||||||
#include "vrenvironment.hpp"
|
|
||||||
#include "openxrmanager.hpp"
|
#include "openxrmanager.hpp"
|
||||||
|
#include "vrenvironment.hpp"
|
||||||
#include "openxrmanagerimpl.hpp"
|
#include "openxrmanagerimpl.hpp"
|
||||||
#include "../mwinput/inputmanagerimp.hpp"
|
#include "../mwinput/inputmanagerimp.hpp"
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
|
||||||
|
|
||||||
#include <Windows.h>
|
|
||||||
|
|
||||||
#include <openxr/openxr.h>
|
|
||||||
#include <openxr/openxr_platform.h>
|
|
||||||
#include <openxr/openxr_platform_defines.h>
|
|
||||||
#include <openxr/openxr_reflection.h>
|
|
||||||
|
|
||||||
#include <osg/Camera>
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <array>
|
|
||||||
#include <map>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
@ -82,7 +67,7 @@ namespace MWVR
|
||||||
try {
|
try {
|
||||||
mPrivate = std::make_shared<OpenXRManagerImpl>();
|
mPrivate = std::make_shared<OpenXRManagerImpl>();
|
||||||
}
|
}
|
||||||
catch (std::exception & e)
|
catch (std::exception& e)
|
||||||
{
|
{
|
||||||
Log(Debug::Error) << "Exception thrown by OpenXR: " << e.what();
|
Log(Debug::Error) << "Exception thrown by OpenXR: " << e.what();
|
||||||
osg::ref_ptr<osg::State> state = gc->getState();
|
osg::ref_ptr<osg::State> state = gc->getState();
|
||||||
|
|
|
@ -20,16 +20,16 @@ struct XrCompositionLayerBaseHeader;
|
||||||
|
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
// Use the pimpl pattern to avoid cluttering the namespace with openxr dependencies.
|
|
||||||
class OpenXRManagerImpl;
|
class OpenXRManagerImpl;
|
||||||
|
|
||||||
|
/// \brief Manage the openxr runtime and session
|
||||||
class OpenXRManager : public osg::Referenced
|
class OpenXRManager : public osg::Referenced
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
class RealizeOperation : public osg::GraphicsOperation
|
class RealizeOperation : public osg::GraphicsOperation
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RealizeOperation() : osg::GraphicsOperation("OpenXRRealizeOperation", false){};
|
RealizeOperation() : osg::GraphicsOperation("OpenXRRealizeOperation", false) {};
|
||||||
void operator()(osg::GraphicsContext* gc) override;
|
void operator()(osg::GraphicsContext* gc) override;
|
||||||
virtual bool realized();
|
virtual bool realized();
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "openxrmanagerimpl.hpp"
|
#include "openxrmanagerimpl.hpp"
|
||||||
|
|
||||||
#include "openxrswapchain.hpp"
|
#include "openxrswapchain.hpp"
|
||||||
#include "openxrswapchainimpl.hpp"
|
#include "openxrswapchainimpl.hpp"
|
||||||
#include "vrtexture.hpp"
|
#include "vrtexture.hpp"
|
||||||
|
@ -298,7 +299,7 @@ namespace MWVR
|
||||||
xrLayer.pose = toXR(layer.pose);
|
xrLayer.pose = toXR(layer.pose);
|
||||||
xrLayer.fov = toXR(layer.fov);
|
xrLayer.fov = toXR(layer.fov);
|
||||||
xrLayer.next = nullptr;
|
xrLayer.next = nullptr;
|
||||||
|
|
||||||
return xrLayer;
|
return xrLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,7 +332,7 @@ namespace MWVR
|
||||||
wglMakeCurrent(DC, GLRC);
|
wglMakeCurrent(DC, GLRC);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<View, 2>
|
std::array<View, 2>
|
||||||
OpenXRManagerImpl::getPredictedViews(
|
OpenXRManagerImpl::getPredictedViews(
|
||||||
int64_t predictedDisplayTime,
|
int64_t predictedDisplayTime,
|
||||||
ReferenceSpace space)
|
ReferenceSpace space)
|
||||||
|
@ -444,7 +445,7 @@ namespace MWVR
|
||||||
switch (newState)
|
switch (newState)
|
||||||
{
|
{
|
||||||
case XR_SESSION_STATE_READY:
|
case XR_SESSION_STATE_READY:
|
||||||
//case XR_SESSION_STATE_IDLE:
|
//case XR_SESSION_STATE_IDLE:
|
||||||
{
|
{
|
||||||
XrSessionBeginInfo beginInfo{ XR_TYPE_SESSION_BEGIN_INFO };
|
XrSessionBeginInfo beginInfo{ XR_TYPE_SESSION_BEGIN_INFO };
|
||||||
beginInfo.primaryViewConfigurationType = mViewConfigType;
|
beginInfo.primaryViewConfigurationType = mViewConfigType;
|
||||||
|
|
|
@ -25,79 +25,80 @@
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
|
||||||
// Error management macros and functions. Should be used on every openxr call.
|
// Error management macros and functions. Should be used on every openxr call.
|
||||||
#define CHK_STRINGIFY(x) #x
|
#define CHK_STRINGIFY(x) #x
|
||||||
#define TOSTRING(x) CHK_STRINGIFY(x)
|
#define TOSTRING(x) CHK_STRINGIFY(x)
|
||||||
#define FILE_AND_LINE __FILE__ ":" TOSTRING(__LINE__)
|
#define FILE_AND_LINE __FILE__ ":" TOSTRING(__LINE__)
|
||||||
#define CHECK_XRCMD(cmd) CheckXrResult(cmd, #cmd, FILE_AND_LINE);
|
#define CHECK_XRCMD(cmd) CheckXrResult(cmd, #cmd, FILE_AND_LINE);
|
||||||
#define CHECK_XRRESULT(res, cmdStr) CheckXrResult(res, cmdStr, FILE_AND_LINE);
|
#define CHECK_XRRESULT(res, cmdStr) CheckXrResult(res, cmdStr, FILE_AND_LINE);
|
||||||
XrResult CheckXrResult(XrResult res, const char* originator = nullptr, const char* sourceLocation = nullptr);
|
XrResult CheckXrResult(XrResult res, const char* originator = nullptr, const char* sourceLocation = nullptr);
|
||||||
std::string XrResultString(XrResult res);
|
std::string XrResultString(XrResult res);
|
||||||
|
|
||||||
/// Conversion methods from openxr types to osg/mwvr types. Includes managing the differing conventions.
|
/// Conversion methods from openxr types to osg/mwvr types. Includes managing the differing conventions.
|
||||||
MWVR::Pose fromXR(XrPosef pose);
|
MWVR::Pose fromXR(XrPosef pose);
|
||||||
MWVR::FieldOfView fromXR(XrFovf fov);
|
MWVR::FieldOfView fromXR(XrFovf fov);
|
||||||
osg::Vec3 fromXR(XrVector3f);
|
osg::Vec3 fromXR(XrVector3f);
|
||||||
osg::Quat fromXR(XrQuaternionf quat);
|
osg::Quat fromXR(XrQuaternionf quat);
|
||||||
|
|
||||||
/// Conversion methods from osg/mwvr types to openxr types. Includes managing the differing conventions.
|
/// Conversion methods from osg/mwvr types to openxr types. Includes managing the differing conventions.
|
||||||
XrPosef toXR(MWVR::Pose pose);
|
XrPosef toXR(MWVR::Pose pose);
|
||||||
XrFovf toXR(MWVR::FieldOfView fov);
|
XrFovf toXR(MWVR::FieldOfView fov);
|
||||||
XrVector3f toXR(osg::Vec3 v);
|
XrVector3f toXR(osg::Vec3 v);
|
||||||
XrQuaternionf toXR(osg::Quat quat);
|
XrQuaternionf toXR(osg::Quat quat);
|
||||||
|
|
||||||
XrCompositionLayerProjectionView toXR(MWVR::CompositionLayerProjectionView layer);
|
XrCompositionLayerProjectionView toXR(MWVR::CompositionLayerProjectionView layer);
|
||||||
|
|
||||||
struct OpenXRManagerImpl
|
/// \brief Implementation of OpenXRManager
|
||||||
{
|
struct OpenXRManagerImpl
|
||||||
OpenXRManagerImpl(void);
|
{
|
||||||
~OpenXRManagerImpl(void);
|
OpenXRManagerImpl(void);
|
||||||
|
~OpenXRManagerImpl(void);
|
||||||
|
|
||||||
void waitFrame();
|
void waitFrame();
|
||||||
void beginFrame();
|
void beginFrame();
|
||||||
void endFrame(int64_t displayTime, int layerCount, const std::array<CompositionLayerProjectionView, 2>& layerStack);
|
void endFrame(int64_t displayTime, int layerCount, const std::array<CompositionLayerProjectionView, 2>& layerStack);
|
||||||
bool xrSessionRunning() const { return mSessionRunning; }
|
bool xrSessionRunning() const { return mSessionRunning; }
|
||||||
std::array<View, 2> getPredictedViews(int64_t predictedDisplayTime, ReferenceSpace space);
|
std::array<View, 2> getPredictedViews(int64_t predictedDisplayTime, ReferenceSpace space);
|
||||||
MWVR::Pose getPredictedHeadPose(int64_t predictedDisplayTime, ReferenceSpace space);
|
MWVR::Pose getPredictedHeadPose(int64_t predictedDisplayTime, ReferenceSpace space);
|
||||||
void handleEvents();
|
void handleEvents();
|
||||||
void enablePredictions();
|
void enablePredictions();
|
||||||
void disablePredictions();
|
void disablePredictions();
|
||||||
long long getLastPredictedDisplayTime();
|
long long getLastPredictedDisplayTime();
|
||||||
long long getLastPredictedDisplayPeriod();
|
long long getLastPredictedDisplayPeriod();
|
||||||
std::array<SwapchainConfig, 2> getRecommendedSwapchainConfig() const;
|
std::array<SwapchainConfig, 2> getRecommendedSwapchainConfig() const;
|
||||||
XrSpace getReferenceSpace(ReferenceSpace space);
|
XrSpace getReferenceSpace(ReferenceSpace space);
|
||||||
XrSession xrSession() const { return mSession; };
|
XrSession xrSession() const { return mSession; };
|
||||||
XrInstance xrInstance() const { return mInstance; };
|
XrInstance xrInstance() const { return mInstance; };
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void LogLayersAndExtensions();
|
void LogLayersAndExtensions();
|
||||||
void LogInstanceInfo();
|
void LogInstanceInfo();
|
||||||
void LogReferenceSpaces();
|
void LogReferenceSpaces();
|
||||||
const XrEventDataBaseHeader* nextEvent();
|
const XrEventDataBaseHeader* nextEvent();
|
||||||
void HandleSessionStateChanged(const XrEventDataSessionStateChanged& stateChangedEvent);
|
void HandleSessionStateChanged(const XrEventDataSessionStateChanged& stateChangedEvent);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
bool mPredictionsEnabled = false;
|
bool mPredictionsEnabled = false;
|
||||||
XrInstance mInstance = XR_NULL_HANDLE;
|
XrInstance mInstance = XR_NULL_HANDLE;
|
||||||
XrSession mSession = XR_NULL_HANDLE;
|
XrSession mSession = XR_NULL_HANDLE;
|
||||||
XrSpace mSpace = XR_NULL_HANDLE;
|
XrSpace mSpace = XR_NULL_HANDLE;
|
||||||
XrFormFactor mFormFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
|
XrFormFactor mFormFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
|
||||||
XrViewConfigurationType mViewConfigType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
|
XrViewConfigurationType mViewConfigType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
|
||||||
XrEnvironmentBlendMode mEnvironmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
|
XrEnvironmentBlendMode mEnvironmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
|
||||||
XrSystemId mSystemId = XR_NULL_SYSTEM_ID;
|
XrSystemId mSystemId = XR_NULL_SYSTEM_ID;
|
||||||
XrGraphicsBindingOpenGLWin32KHR mGraphicsBinding{ XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR };
|
XrGraphicsBindingOpenGLWin32KHR mGraphicsBinding{ XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR };
|
||||||
XrSystemProperties mSystemProperties{ XR_TYPE_SYSTEM_PROPERTIES };
|
XrSystemProperties mSystemProperties{ XR_TYPE_SYSTEM_PROPERTIES };
|
||||||
std::array<XrViewConfigurationView, 2> mConfigViews{ { {XR_TYPE_VIEW_CONFIGURATION_VIEW}, {XR_TYPE_VIEW_CONFIGURATION_VIEW} } };
|
std::array<XrViewConfigurationView, 2> mConfigViews{ { {XR_TYPE_VIEW_CONFIGURATION_VIEW}, {XR_TYPE_VIEW_CONFIGURATION_VIEW} } };
|
||||||
XrSpace mReferenceSpaceView = XR_NULL_HANDLE;
|
XrSpace mReferenceSpaceView = XR_NULL_HANDLE;
|
||||||
XrSpace mReferenceSpaceStage = XR_NULL_HANDLE;
|
XrSpace mReferenceSpaceStage = XR_NULL_HANDLE;
|
||||||
XrEventDataBuffer mEventDataBuffer{ XR_TYPE_EVENT_DATA_BUFFER };
|
XrEventDataBuffer mEventDataBuffer{ XR_TYPE_EVENT_DATA_BUFFER };
|
||||||
XrFrameState mFrameState{};
|
XrFrameState mFrameState{};
|
||||||
XrSessionState mSessionState = XR_SESSION_STATE_UNKNOWN;
|
XrSessionState mSessionState = XR_SESSION_STATE_UNKNOWN;
|
||||||
bool mSessionRunning = false;
|
bool mSessionRunning = false;
|
||||||
std::mutex mFrameStateMutex{};
|
std::mutex mFrameStateMutex{};
|
||||||
std::mutex mEventMutex{};
|
std::mutex mEventMutex{};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -10,6 +10,7 @@ namespace MWVR
|
||||||
{
|
{
|
||||||
class OpenXRSwapchainImpl;
|
class OpenXRSwapchainImpl;
|
||||||
|
|
||||||
|
/// \brief Creation and management of openxr swapchains
|
||||||
class OpenXRSwapchain
|
class OpenXRSwapchain
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -11,8 +11,6 @@
|
||||||
#include <openxr/openxr_reflection.h>
|
#include <openxr/openxr_reflection.h>
|
||||||
|
|
||||||
namespace MWVR {
|
namespace MWVR {
|
||||||
|
|
||||||
|
|
||||||
OpenXRSwapchainImpl::OpenXRSwapchainImpl(osg::ref_ptr<osg::State> state, SwapchainConfig config)
|
OpenXRSwapchainImpl::OpenXRSwapchainImpl(osg::ref_ptr<osg::State> state, SwapchainConfig config)
|
||||||
: mWidth((int)config.recommendedWidth)
|
: mWidth((int)config.recommendedWidth)
|
||||||
, mHeight((int)config.recommendedHeight)
|
, mHeight((int)config.recommendedHeight)
|
||||||
|
@ -33,27 +31,45 @@ namespace MWVR {
|
||||||
std::vector<int64_t> swapchainFormats(swapchainFormatCount);
|
std::vector<int64_t> swapchainFormats(swapchainFormatCount);
|
||||||
CHECK_XRCMD(xrEnumerateSwapchainFormats(xr->impl().xrSession(), (uint32_t)swapchainFormats.size(), &swapchainFormatCount, swapchainFormats.data()));
|
CHECK_XRCMD(xrEnumerateSwapchainFormats(xr->impl().xrSession(), (uint32_t)swapchainFormats.size(), &swapchainFormatCount, swapchainFormats.data()));
|
||||||
|
|
||||||
// List of supported color swapchain formats.
|
// Find supported color swapchain format.
|
||||||
constexpr int64_t SupportedColorSwapchainFormats[] = {
|
constexpr int64_t RequestedColorSwapchainFormats[] = {
|
||||||
GL_RGBA8,
|
GL_RGBA8,
|
||||||
GL_RGBA8_SNORM,
|
GL_RGBA8_SNORM,
|
||||||
};
|
};
|
||||||
|
|
||||||
auto swapchainFormatIt =
|
auto swapchainFormatIt =
|
||||||
std::find_first_of(swapchainFormats.begin(), swapchainFormats.end(), std::begin(SupportedColorSwapchainFormats),
|
std::find_first_of(swapchainFormats.begin(), swapchainFormats.end(), std::begin(RequestedColorSwapchainFormats),
|
||||||
std::end(SupportedColorSwapchainFormats));
|
std::end(RequestedColorSwapchainFormats));
|
||||||
if (swapchainFormatIt == swapchainFormats.end()) {
|
if (swapchainFormatIt == swapchainFormats.end()) {
|
||||||
Log(Debug::Error) << "No swapchain format supported at runtime";
|
throw std::runtime_error("Swapchain color format not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
mSwapchainColorFormat = *swapchainFormatIt;
|
mSwapchainColorFormat = *swapchainFormatIt;
|
||||||
|
|
||||||
|
// Find supported depth swapchain format.
|
||||||
|
constexpr int64_t RequestedDepthSwapchainFormats[] = {
|
||||||
|
GL_DEPTH_COMPONENT32F,
|
||||||
|
GL_DEPTH_COMPONENT24,
|
||||||
|
GL_DEPTH_COMPONENT16,
|
||||||
|
};
|
||||||
|
|
||||||
|
swapchainFormatIt =
|
||||||
|
std::find_first_of(swapchainFormats.begin(), swapchainFormats.end(), std::begin(RequestedDepthSwapchainFormats),
|
||||||
|
std::end(RequestedDepthSwapchainFormats));
|
||||||
|
if (swapchainFormatIt == swapchainFormats.end()) {
|
||||||
|
throw std::runtime_error("Swapchain depth format not supported");
|
||||||
|
}
|
||||||
|
mSwapchainDepthFormat = *swapchainFormatIt;
|
||||||
|
|
||||||
mSamples = Settings::Manager::getInt("antialiasing", "Video");
|
mSamples = Settings::Manager::getInt("antialiasing", "Video");
|
||||||
|
// OpenXR requires a non-zero value
|
||||||
|
if (mSamples < 1)
|
||||||
|
mSamples = 1;
|
||||||
|
|
||||||
|
|
||||||
while (mSamples > 0)
|
while (mSamples > 0)
|
||||||
{
|
{
|
||||||
Log(Debug::Verbose) << "Creating swapchain with dimensions Width=" << mWidth << " Heigh=" << mHeight << " SampleCount=" << mSamples;
|
Log(Debug::Verbose) << "Creating swapchain with dimensions Width=" << mWidth << " Heigh=" << mHeight << " SampleCount=" << mSamples;
|
||||||
// Create the swapchain.
|
// First create the swapchain of color buffers.
|
||||||
XrSwapchainCreateInfo swapchainCreateInfo{ XR_TYPE_SWAPCHAIN_CREATE_INFO };
|
XrSwapchainCreateInfo swapchainCreateInfo{ XR_TYPE_SWAPCHAIN_CREATE_INFO };
|
||||||
swapchainCreateInfo.arraySize = 1;
|
swapchainCreateInfo.arraySize = 1;
|
||||||
swapchainCreateInfo.format = mSwapchainColorFormat;
|
swapchainCreateInfo.format = mSwapchainColorFormat;
|
||||||
|
@ -61,30 +77,38 @@ namespace MWVR {
|
||||||
swapchainCreateInfo.height = mHeight;
|
swapchainCreateInfo.height = mHeight;
|
||||||
swapchainCreateInfo.mipCount = 1;
|
swapchainCreateInfo.mipCount = 1;
|
||||||
swapchainCreateInfo.faceCount = 1;
|
swapchainCreateInfo.faceCount = 1;
|
||||||
swapchainCreateInfo.sampleCount = 1;
|
swapchainCreateInfo.sampleCount = mSamples;
|
||||||
swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
|
swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
|
||||||
//CHECK_XRCMD(xrCreateSwapchain(xr->impl().xrSession(), &swapchainCreateInfo, &mSwapchain));
|
|
||||||
auto res = xrCreateSwapchain(xr->impl().xrSession(), &swapchainCreateInfo, &mSwapchain);
|
auto res = xrCreateSwapchain(xr->impl().xrSession(), &swapchainCreateInfo, &mSwapchain);
|
||||||
if (XR_SUCCEEDED(res))
|
if (!XR_SUCCEEDED(res))
|
||||||
break;
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
Log(Debug::Verbose) << "Failed to create swapchain with SampleCount=" << mSamples << ": " << XrResultString(res);
|
Log(Debug::Verbose) << "Failed to create swapchain with SampleCount=" << mSamples << ": " << XrResultString(res);
|
||||||
mSamples /= 2;
|
mSamples /= 2;
|
||||||
if(mSamples == 0)
|
if (mSamples == 0)
|
||||||
std::runtime_error(XrResultString(res));
|
throw std::runtime_error(XrResultString(res));
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
// Now create the swapchain of depth buffers.
|
||||||
|
swapchainCreateInfo.format = mSwapchainDepthFormat;
|
||||||
|
swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||||
|
res = xrCreateSwapchain(xr->impl().xrSession(), &swapchainCreateInfo, &mDepthSwapchain);
|
||||||
|
if (XR_SUCCEEDED(res))
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
throw std::runtime_error(XrResultString(res));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t imageCount = 0;
|
uint32_t imageCount = 0;
|
||||||
CHECK_XRCMD(xrEnumerateSwapchainImages(mSwapchain, 0, &imageCount, nullptr));
|
CHECK_XRCMD(xrEnumerateSwapchainImages(mSwapchain, 0, &imageCount, nullptr));
|
||||||
|
|
||||||
mSwapchainImageBuffers.resize(imageCount, { XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR });
|
mSwapchainImageBuffers.resize(imageCount, { XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR });
|
||||||
CHECK_XRCMD(xrEnumerateSwapchainImages(mSwapchain, imageCount, &imageCount, reinterpret_cast<XrSwapchainImageBaseHeader*>(mSwapchainImageBuffers.data())));
|
CHECK_XRCMD(xrEnumerateSwapchainImages(mSwapchain, imageCount, &imageCount, reinterpret_cast<XrSwapchainImageBaseHeader*>(mSwapchainImageBuffers.data())));
|
||||||
//for (const auto& swapchainImage : mSwapchainImageBuffers)
|
|
||||||
// mTextureBuffers.push_back(new VRTexture(state, swapchainImage.image, mWidth, mHeight, 0));
|
CHECK_XRCMD(xrEnumerateSwapchainImages(mDepthSwapchain, 0, &imageCount, nullptr));
|
||||||
|
mDepthSwapchainImageBuffers.resize(imageCount, { XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR });
|
||||||
|
CHECK_XRCMD(xrEnumerateSwapchainImages(mDepthSwapchain, imageCount, &imageCount, reinterpret_cast<XrSwapchainImageBaseHeader*>(mDepthSwapchainImageBuffers.data())));
|
||||||
|
|
||||||
for (unsigned i = 0; i < imageCount; i++)
|
for (unsigned i = 0; i < imageCount; i++)
|
||||||
mRenderBuffers.emplace_back(new VRTexture(state, mWidth, mHeight, mSamples));
|
mRenderBuffers.emplace_back(new VRTexture(state, mWidth, mHeight, mSamples, mSwapchainImageBuffers[i].image, mDepthSwapchainImageBuffers[i].image));
|
||||||
|
|
||||||
mSubImage.swapchain = mSwapchain;
|
mSubImage.swapchain = mSwapchain;
|
||||||
mSubImage.imageRect.offset = { 0, 0 };
|
mSubImage.imageRect.offset = { 0, 0 };
|
||||||
|
@ -99,12 +123,14 @@ namespace MWVR {
|
||||||
|
|
||||||
VRTexture* OpenXRSwapchainImpl::renderBuffer() const
|
VRTexture* OpenXRSwapchainImpl::renderBuffer() const
|
||||||
{
|
{
|
||||||
return mRenderBuffers[mRenderBuffer].get();
|
if (isAcquired())
|
||||||
|
return mRenderBuffers[mAcquiredImageIndex].get();
|
||||||
|
throw std::logic_error("Swapbuffer not acquired before use");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t OpenXRSwapchainImpl::acquiredImage() const
|
uint32_t OpenXRSwapchainImpl::acquiredImage() const
|
||||||
{
|
{
|
||||||
if(isAcquired())
|
if (isAcquired())
|
||||||
return mSwapchainImageBuffers[mAcquiredImageIndex].image;
|
return mSwapchainImageBuffers[mAcquiredImageIndex].image;
|
||||||
throw std::logic_error("Swapbuffer not acquired before use");
|
throw std::logic_error("Swapbuffer not acquired before use");
|
||||||
}
|
}
|
||||||
|
@ -116,7 +142,7 @@ namespace MWVR {
|
||||||
|
|
||||||
void OpenXRSwapchainImpl::beginFrame(osg::GraphicsContext* gc)
|
void OpenXRSwapchainImpl::beginFrame(osg::GraphicsContext* gc)
|
||||||
{
|
{
|
||||||
mRenderBuffer = (mRenderBuffer + 1) % mRenderBuffers.size();
|
acquire(gc);
|
||||||
renderBuffer()->beginFrame(gc);
|
renderBuffer()->beginFrame(gc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,29 +150,34 @@ namespace MWVR {
|
||||||
|
|
||||||
void OpenXRSwapchainImpl::endFrame(osg::GraphicsContext* gc)
|
void OpenXRSwapchainImpl::endFrame(osg::GraphicsContext* gc)
|
||||||
{
|
{
|
||||||
// Blit frame to swapchain
|
|
||||||
acquire(gc);
|
|
||||||
renderBuffer()->endFrame(gc, acquiredImage());
|
|
||||||
release(gc);
|
release(gc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenXRSwapchainImpl::acquire(osg::GraphicsContext* gc)
|
void OpenXRSwapchainImpl::acquire(osg::GraphicsContext*)
|
||||||
{
|
{
|
||||||
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
|
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
|
||||||
|
// I am trusting that the openxr runtime won't diverge these indices so long as these are always called together.
|
||||||
|
// If some dumb ass implementation decides to violate this we'll just have to work around that if it actually happens.
|
||||||
CHECK_XRCMD(xrAcquireSwapchainImage(mSwapchain, &acquireInfo, &mAcquiredImageIndex));
|
CHECK_XRCMD(xrAcquireSwapchainImage(mSwapchain, &acquireInfo, &mAcquiredImageIndex));
|
||||||
|
uint32_t depthIndex = 0;
|
||||||
|
CHECK_XRCMD(xrAcquireSwapchainImage(mDepthSwapchain, &acquireInfo, &depthIndex));
|
||||||
|
if (depthIndex != mAcquiredImageIndex)
|
||||||
|
Log(Debug::Warning) << "Depth and color indices diverged";
|
||||||
|
|
||||||
XrSwapchainImageWaitInfo waitInfo{ XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
|
XrSwapchainImageWaitInfo waitInfo{ XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
|
||||||
waitInfo.timeout = XR_INFINITE_DURATION;
|
waitInfo.timeout = XR_INFINITE_DURATION;
|
||||||
CHECK_XRCMD(xrWaitSwapchainImage(mSwapchain, &waitInfo));
|
CHECK_XRCMD(xrWaitSwapchainImage(mSwapchain, &waitInfo));
|
||||||
|
CHECK_XRCMD(xrWaitSwapchainImage(mDepthSwapchain, &waitInfo));
|
||||||
|
|
||||||
mIsAcquired = true;
|
mIsAcquired = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenXRSwapchainImpl::release(osg::GraphicsContext* gc)
|
void OpenXRSwapchainImpl::release(osg::GraphicsContext*)
|
||||||
{
|
{
|
||||||
mIsAcquired = false;
|
mIsAcquired = false;
|
||||||
|
|
||||||
XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
|
XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
|
||||||
CHECK_XRCMD(xrReleaseSwapchainImage(mSwapchain, &releaseInfo));
|
CHECK_XRCMD(xrReleaseSwapchainImage(mSwapchain, &releaseInfo));
|
||||||
|
CHECK_XRCMD(xrReleaseSwapchainImage(mDepthSwapchain, &releaseInfo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,43 +8,45 @@ struct XrSwapchainSubImage;
|
||||||
|
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
/// \brief Implementation of OpenXRSwapchain
|
||||||
|
class OpenXRSwapchainImpl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OpenXRSwapchainImpl(osg::ref_ptr<osg::State> state, SwapchainConfig config);
|
||||||
|
~OpenXRSwapchainImpl();
|
||||||
|
|
||||||
class OpenXRSwapchainImpl
|
void beginFrame(osg::GraphicsContext* gc);
|
||||||
{
|
void endFrame(osg::GraphicsContext* gc);
|
||||||
public:
|
void acquire(osg::GraphicsContext* gc);
|
||||||
OpenXRSwapchainImpl(osg::ref_ptr<osg::State> state, SwapchainConfig config);
|
void release(osg::GraphicsContext* gc);
|
||||||
~OpenXRSwapchainImpl();
|
|
||||||
|
|
||||||
void beginFrame(osg::GraphicsContext* gc);
|
VRTexture* renderBuffer() const;
|
||||||
void endFrame(osg::GraphicsContext* gc);
|
|
||||||
void acquire(osg::GraphicsContext* gc);
|
|
||||||
void release(osg::GraphicsContext* gc);
|
|
||||||
|
|
||||||
VRTexture* renderBuffer() const;
|
uint32_t acquiredImage() const;
|
||||||
|
|
||||||
uint32_t acquiredImage() const;
|
bool isAcquired() const;
|
||||||
|
XrSwapchain xrSwapchain(void) const { return mSwapchain; };
|
||||||
|
XrSwapchainSubImage xrSubImage(void) const { return mSubImage; };
|
||||||
|
int width() const { return mWidth; };
|
||||||
|
int height() const { return mHeight; };
|
||||||
|
int samples() const { return mSamples; };
|
||||||
|
|
||||||
bool isAcquired() const;
|
XrSwapchain mSwapchain = XR_NULL_HANDLE;
|
||||||
XrSwapchain xrSwapchain(void) const { return mSwapchain; };
|
XrSwapchain mDepthSwapchain = XR_NULL_HANDLE;
|
||||||
XrSwapchainSubImage xrSubImage(void) const { return mSubImage; };
|
std::vector<XrSwapchainImageOpenGLKHR> mSwapchainImageBuffers{};
|
||||||
int width() const { return mWidth; };
|
std::vector<XrSwapchainImageOpenGLKHR> mDepthSwapchainImageBuffers{};
|
||||||
int height() const { return mHeight; };
|
XrSwapchainSubImage mSubImage{};
|
||||||
int samples() const { return mSamples; };
|
int32_t mWidth = -1;
|
||||||
|
int32_t mHeight = -1;
|
||||||
XrSwapchain mSwapchain = XR_NULL_HANDLE;
|
int32_t mSamples = -1;
|
||||||
std::vector<XrSwapchainImageOpenGLKHR> mSwapchainImageBuffers{};
|
int64_t mSwapchainColorFormat = -1;
|
||||||
//std::vector<osg::ref_ptr<VRTexture> > mTextureBuffers{};
|
int64_t mSwapchainDepthFormat = -1;
|
||||||
XrSwapchainSubImage mSubImage{};
|
uint32_t mFBO = 0;
|
||||||
int32_t mWidth = -1;
|
std::vector<std::unique_ptr<VRTexture> > mRenderBuffers{};
|
||||||
int32_t mHeight = -1;
|
int mRenderBuffer{ 0 };
|
||||||
int32_t mSamples = -1;
|
uint32_t mAcquiredImageIndex{ 0 };
|
||||||
int64_t mSwapchainColorFormat = -1;
|
bool mIsAcquired{ false };
|
||||||
uint32_t mFBO = 0;
|
};
|
||||||
std::vector<std::unique_ptr<VRTexture> > mRenderBuffers{};
|
|
||||||
int mRenderBuffer{ 0 };
|
|
||||||
uint32_t mAcquiredImageIndex{ 0 };
|
|
||||||
bool mIsAcquired{ false };
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -33,324 +33,326 @@
|
||||||
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
|
||||||
namespace MWVR { namespace RealisticCombat {
|
namespace MWVR {
|
||||||
|
namespace RealisticCombat {
|
||||||
|
|
||||||
static const char* stateToString(SwingState florida)
|
static const char* stateToString(SwingState florida)
|
||||||
{
|
|
||||||
switch (florida)
|
|
||||||
{
|
|
||||||
case SwingState_Cooldown:
|
|
||||||
return "Cooldown";
|
|
||||||
case SwingState_Impact:
|
|
||||||
return "Impact";
|
|
||||||
case SwingState_Ready:
|
|
||||||
return "Ready";
|
|
||||||
case SwingState_Swing:
|
|
||||||
return "Swing";
|
|
||||||
case SwingState_Launch:
|
|
||||||
return "Launch";
|
|
||||||
}
|
|
||||||
return "Error, invalid enum";
|
|
||||||
}
|
|
||||||
static const char* swingTypeToString(int type)
|
|
||||||
{
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case ESM::Weapon::AT_Chop:
|
|
||||||
return "Chop";
|
|
||||||
case ESM::Weapon::AT_Slash:
|
|
||||||
return "Slash";
|
|
||||||
case ESM::Weapon::AT_Thrust:
|
|
||||||
return "Thrust";
|
|
||||||
case -1:
|
|
||||||
return "Fail";
|
|
||||||
default:
|
|
||||||
return "Invalid";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StateMachine::StateMachine(MWWorld::Ptr ptr) : ptr(ptr) {}
|
|
||||||
|
|
||||||
bool StateMachine::canSwing()
|
|
||||||
{
|
|
||||||
if (swingType >= 0)
|
|
||||||
if (velocity >= minVelocity)
|
|
||||||
if (swingType != ESM::Weapon::AT_Thrust || thrustVelocity >= 0.f)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actions common to all transitions
|
|
||||||
void StateMachine::transition(
|
|
||||||
SwingState newState)
|
|
||||||
{
|
|
||||||
Log(Debug::Verbose) << "Transition:" << stateToString(state) << "->" << stateToString(newState);
|
|
||||||
maxSwingVelocity = 0.f;
|
|
||||||
timeSinceEnteredState = 0.f;
|
|
||||||
movementSinceEnteredState = 0.f;
|
|
||||||
state = newState;
|
|
||||||
}
|
|
||||||
|
|
||||||
void StateMachine::reset()
|
|
||||||
{
|
|
||||||
maxSwingVelocity = 0.f;
|
|
||||||
timeSinceEnteredState = 0.f;
|
|
||||||
velocity = 0.f;
|
|
||||||
previousPosition = osg::Vec3(0.f, 0.f, 0.f);
|
|
||||||
state = SwingState_Ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isMeleeWeapon(int type)
|
|
||||||
{
|
|
||||||
if (MWMechanics::getWeaponType(type)->mWeaponClass != ESM::WeaponType::Melee)
|
|
||||||
return false;
|
|
||||||
if (type == ESM::Weapon::HandToHand)
|
|
||||||
return true;
|
|
||||||
if (type >= 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isSideSwingValidForWeapon(int type)
|
|
||||||
{
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case ESM::Weapon::HandToHand:
|
|
||||||
case ESM::Weapon::BluntOneHand:
|
|
||||||
case ESM::Weapon::BluntTwoClose:
|
|
||||||
case ESM::Weapon::BluntTwoWide:
|
|
||||||
case ESM::Weapon::SpearTwoWide:
|
|
||||||
return true;
|
|
||||||
case ESM::Weapon::ShortBladeOneHand:
|
|
||||||
case ESM::Weapon::LongBladeOneHand:
|
|
||||||
case ESM::Weapon::LongBladeTwoHand:
|
|
||||||
case ESM::Weapon::AxeOneHand:
|
|
||||||
case ESM::Weapon::AxeTwoHand:
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void StateMachine::update(float dt, bool enabled)
|
|
||||||
{
|
|
||||||
auto* session = Environment::get().getSession();
|
|
||||||
auto* world = MWBase::Environment::get().getWorld();
|
|
||||||
auto& predictedPoses = session->predictedPoses(VRSession::FramePhase::Update);
|
|
||||||
auto& handPose = predictedPoses.hands[(int)MWVR::Side::RIGHT_SIDE];
|
|
||||||
auto weaponType = world->getActiveWeaponType();
|
|
||||||
|
|
||||||
enabled = enabled && isMeleeWeapon(weaponType);
|
|
||||||
|
|
||||||
if (mEnabled != enabled)
|
|
||||||
{
|
|
||||||
reset();
|
|
||||||
mEnabled = enabled;
|
|
||||||
}
|
|
||||||
if (!enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
timeSinceEnteredState += dt;
|
|
||||||
|
|
||||||
|
|
||||||
// First determine direction of different swing types
|
|
||||||
|
|
||||||
// Discover orientation of weapon
|
|
||||||
osg::Quat weaponDir = handPose.orientation;
|
|
||||||
|
|
||||||
// Morrowind models do not hold weapons at a natural angle, so i rotate the hand forward
|
|
||||||
// to get a more natural angle on the weapon to allow more comfortable combat.
|
|
||||||
if (weaponType != ESM::Weapon::HandToHand)
|
|
||||||
weaponDir = osg::Quat(osg::PI_4, osg::Vec3{ 1,0,0 }) * weaponDir;
|
|
||||||
|
|
||||||
// Thrust means stabbing in the direction of the weapon
|
|
||||||
osg::Vec3 thrustDirection = weaponDir * osg::Vec3{ 0,1,0 };
|
|
||||||
|
|
||||||
// Slash and Chop are vertical, relative to the orientation of the weapon (direction of the sharp edge / hammer)
|
|
||||||
osg::Vec3 slashChopDirection = weaponDir * osg::Vec3{ 0,0,1 };
|
|
||||||
|
|
||||||
// Side direction of the weapon (i.e. The blunt side of the sword)
|
|
||||||
osg::Vec3 sideDirection = weaponDir * osg::Vec3{ 1,0,0 };
|
|
||||||
|
|
||||||
|
|
||||||
// Next determine current hand movement
|
|
||||||
|
|
||||||
// If tracking is lost, openxr will return a position of 0
|
|
||||||
// So i reset position when tracking is re-acquired to avoid a superspeed strike.
|
|
||||||
// Theoretically, the player's hand really could be at 0,0,0
|
|
||||||
// but that's a super rare case so whatever.
|
|
||||||
if (previousPosition == osg::Vec3(0.f, 0.f, 0.f))
|
|
||||||
previousPosition = handPose.position;
|
|
||||||
|
|
||||||
osg::Vec3 movement = handPose.position - previousPosition;
|
|
||||||
movementSinceEnteredState += movement.length();
|
|
||||||
previousPosition = handPose.position;
|
|
||||||
osg::Vec3 swingVector = movement / dt;
|
|
||||||
osg::Vec3 swingDirection = swingVector;
|
|
||||||
swingDirection.normalize();
|
|
||||||
|
|
||||||
// Compute swing velocities
|
|
||||||
|
|
||||||
// Thrust follows the orientation of the weapon. Negative thrust = no attack.
|
|
||||||
thrustVelocity = swingVector * thrustDirection;
|
|
||||||
velocity = swingVector.length();
|
|
||||||
|
|
||||||
|
|
||||||
if (isSideSwingValidForWeapon(weaponType))
|
|
||||||
{
|
|
||||||
// Compute velocity in the plane normal to the thrust direction.
|
|
||||||
float thrustComponent = std::abs(thrustVelocity / velocity);
|
|
||||||
float planeComponent = std::sqrt(1 - thrustComponent * thrustComponent);
|
|
||||||
slashChopVelocity = velocity * planeComponent;
|
|
||||||
sideVelocity = -1000.f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If side swing is not valid for the weapon, count slash/chop only along in
|
|
||||||
// the direction of the weapon's edge.
|
|
||||||
slashChopVelocity = std::abs(swingVector * slashChopDirection);
|
|
||||||
sideVelocity = std::abs(swingVector * sideDirection);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
float orientationVerticality = std::abs(thrustDirection * osg::Vec3{ 0,0,1 });
|
|
||||||
float swingVerticality = std::abs(swingDirection * osg::Vec3{ 0,0,1 });
|
|
||||||
|
|
||||||
// Pick swing type based on greatest current velocity
|
|
||||||
// Note i use abs() of thrust velocity to prevent accidentally triggering
|
|
||||||
// chop/slash when player is withdrawing the weapon.
|
|
||||||
if (sideVelocity > std::abs(thrustVelocity) && sideVelocity > slashChopVelocity)
|
|
||||||
{
|
|
||||||
// Player is swinging with the "blunt" side of a weapon that
|
|
||||||
// cannot be used that way.
|
|
||||||
swingType = -1;
|
|
||||||
}
|
|
||||||
else if (std::abs(thrustVelocity) > slashChopVelocity)
|
|
||||||
{
|
|
||||||
swingType = ESM::Weapon::AT_Thrust;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// First check if the weapon is pointing upwards. In which case slash is not
|
|
||||||
// applicable, and the attack must be a chop.
|
|
||||||
if (orientationVerticality > 0.707)
|
|
||||||
swingType = ESM::Weapon::AT_Chop;
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// Next check if the swing is more horizontal or vertical. A slash
|
switch (florida)
|
||||||
// would be more horizontal.
|
{
|
||||||
if(swingVerticality > 0.707)
|
case SwingState_Cooldown:
|
||||||
swingType = ESM::Weapon::AT_Chop;
|
return "Cooldown";
|
||||||
else
|
case SwingState_Impact:
|
||||||
swingType = ESM::Weapon::AT_Slash;
|
return "Impact";
|
||||||
|
case SwingState_Ready:
|
||||||
|
return "Ready";
|
||||||
|
case SwingState_Swing:
|
||||||
|
return "Swing";
|
||||||
|
case SwingState_Launch:
|
||||||
|
return "Launch";
|
||||||
|
}
|
||||||
|
return "Error, invalid enum";
|
||||||
|
}
|
||||||
|
static const char* swingTypeToString(int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ESM::Weapon::AT_Chop:
|
||||||
|
return "Chop";
|
||||||
|
case ESM::Weapon::AT_Slash:
|
||||||
|
return "Slash";
|
||||||
|
case ESM::Weapon::AT_Thrust:
|
||||||
|
return "Thrust";
|
||||||
|
case -1:
|
||||||
|
return "Fail";
|
||||||
|
default:
|
||||||
|
return "Invalid";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StateMachine::StateMachine(MWWorld::Ptr ptr) : ptr(ptr) {}
|
||||||
|
|
||||||
|
bool StateMachine::canSwing()
|
||||||
|
{
|
||||||
|
if (swingType >= 0)
|
||||||
|
if (velocity >= minVelocity)
|
||||||
|
if (swingType != ESM::Weapon::AT_Thrust || thrustVelocity >= 0.f)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions common to all transitions
|
||||||
|
void StateMachine::transition(
|
||||||
|
SwingState newState)
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << "Transition:" << stateToString(state) << "->" << stateToString(newState);
|
||||||
|
maxSwingVelocity = 0.f;
|
||||||
|
timeSinceEnteredState = 0.f;
|
||||||
|
movementSinceEnteredState = 0.f;
|
||||||
|
state = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::reset()
|
||||||
|
{
|
||||||
|
maxSwingVelocity = 0.f;
|
||||||
|
timeSinceEnteredState = 0.f;
|
||||||
|
velocity = 0.f;
|
||||||
|
previousPosition = osg::Vec3(0.f, 0.f, 0.f);
|
||||||
|
state = SwingState_Ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isMeleeWeapon(int type)
|
||||||
|
{
|
||||||
|
if (MWMechanics::getWeaponType(type)->mWeaponClass != ESM::WeaponType::Melee)
|
||||||
|
return false;
|
||||||
|
if (type == ESM::Weapon::HandToHand)
|
||||||
|
return true;
|
||||||
|
if (type >= 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isSideSwingValidForWeapon(int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ESM::Weapon::HandToHand:
|
||||||
|
case ESM::Weapon::BluntOneHand:
|
||||||
|
case ESM::Weapon::BluntTwoClose:
|
||||||
|
case ESM::Weapon::BluntTwoWide:
|
||||||
|
case ESM::Weapon::SpearTwoWide:
|
||||||
|
return true;
|
||||||
|
case ESM::Weapon::ShortBladeOneHand:
|
||||||
|
case ESM::Weapon::LongBladeOneHand:
|
||||||
|
case ESM::Weapon::LongBladeTwoHand:
|
||||||
|
case ESM::Weapon::AxeOneHand:
|
||||||
|
case ESM::Weapon::AxeTwoHand:
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::update(float dt, bool enabled)
|
||||||
|
{
|
||||||
|
auto* session = Environment::get().getSession();
|
||||||
|
auto* world = MWBase::Environment::get().getWorld();
|
||||||
|
auto& predictedPoses = session->predictedPoses(VRSession::FramePhase::Update);
|
||||||
|
auto& handPose = predictedPoses.hands[(int)MWVR::Side::RIGHT_SIDE];
|
||||||
|
auto weaponType = world->getActiveWeaponType();
|
||||||
|
|
||||||
|
enabled = enabled && isMeleeWeapon(weaponType);
|
||||||
|
|
||||||
|
if (mEnabled != enabled)
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
mEnabled = enabled;
|
||||||
|
}
|
||||||
|
if (!enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
timeSinceEnteredState += dt;
|
||||||
|
|
||||||
|
|
||||||
|
// First determine direction of different swing types
|
||||||
|
|
||||||
|
// Discover orientation of weapon
|
||||||
|
osg::Quat weaponDir = handPose.orientation;
|
||||||
|
|
||||||
|
// Morrowind models do not hold weapons at a natural angle, so i rotate the hand forward
|
||||||
|
// to get a more natural angle on the weapon to allow more comfortable combat.
|
||||||
|
if (weaponType != ESM::Weapon::HandToHand)
|
||||||
|
weaponDir = osg::Quat(osg::PI_4, osg::Vec3{ 1,0,0 }) * weaponDir;
|
||||||
|
|
||||||
|
// Thrust means stabbing in the direction of the weapon
|
||||||
|
osg::Vec3 thrustDirection = weaponDir * osg::Vec3{ 0,1,0 };
|
||||||
|
|
||||||
|
// Slash and Chop are vertical, relative to the orientation of the weapon (direction of the sharp edge / hammer)
|
||||||
|
osg::Vec3 slashChopDirection = weaponDir * osg::Vec3{ 0,0,1 };
|
||||||
|
|
||||||
|
// Side direction of the weapon (i.e. The blunt side of the sword)
|
||||||
|
osg::Vec3 sideDirection = weaponDir * osg::Vec3{ 1,0,0 };
|
||||||
|
|
||||||
|
|
||||||
|
// Next determine current hand movement
|
||||||
|
|
||||||
|
// If tracking is lost, openxr will return a position of 0
|
||||||
|
// So i reset position when tracking is re-acquired to avoid a superspeed strike.
|
||||||
|
// Theoretically, the player's hand really could be at 0,0,0
|
||||||
|
// but that's a super rare case so whatever.
|
||||||
|
if (previousPosition == osg::Vec3(0.f, 0.f, 0.f))
|
||||||
|
previousPosition = handPose.position;
|
||||||
|
|
||||||
|
osg::Vec3 movement = handPose.position - previousPosition;
|
||||||
|
movementSinceEnteredState += movement.length();
|
||||||
|
previousPosition = handPose.position;
|
||||||
|
osg::Vec3 swingVector = movement / dt;
|
||||||
|
osg::Vec3 swingDirection = swingVector;
|
||||||
|
swingDirection.normalize();
|
||||||
|
|
||||||
|
// Compute swing velocities
|
||||||
|
|
||||||
|
// Thrust follows the orientation of the weapon. Negative thrust = no attack.
|
||||||
|
thrustVelocity = swingVector * thrustDirection;
|
||||||
|
velocity = swingVector.length();
|
||||||
|
|
||||||
|
|
||||||
|
if (isSideSwingValidForWeapon(weaponType))
|
||||||
|
{
|
||||||
|
// Compute velocity in the plane normal to the thrust direction.
|
||||||
|
float thrustComponent = std::abs(thrustVelocity / velocity);
|
||||||
|
float planeComponent = std::sqrt(1 - thrustComponent * thrustComponent);
|
||||||
|
slashChopVelocity = velocity * planeComponent;
|
||||||
|
sideVelocity = -1000.f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If side swing is not valid for the weapon, count slash/chop only along in
|
||||||
|
// the direction of the weapon's edge.
|
||||||
|
slashChopVelocity = std::abs(swingVector * slashChopDirection);
|
||||||
|
sideVelocity = std::abs(swingVector * sideDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float orientationVerticality = std::abs(thrustDirection * osg::Vec3{ 0,0,1 });
|
||||||
|
float swingVerticality = std::abs(swingDirection * osg::Vec3{ 0,0,1 });
|
||||||
|
|
||||||
|
// Pick swing type based on greatest current velocity
|
||||||
|
// Note i use abs() of thrust velocity to prevent accidentally triggering
|
||||||
|
// chop/slash when player is withdrawing the weapon.
|
||||||
|
if (sideVelocity > std::abs(thrustVelocity) && sideVelocity > slashChopVelocity)
|
||||||
|
{
|
||||||
|
// Player is swinging with the "blunt" side of a weapon that
|
||||||
|
// cannot be used that way.
|
||||||
|
swingType = -1;
|
||||||
|
}
|
||||||
|
else if (std::abs(thrustVelocity) > slashChopVelocity)
|
||||||
|
{
|
||||||
|
swingType = ESM::Weapon::AT_Thrust;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// First check if the weapon is pointing upwards. In which case slash is not
|
||||||
|
// applicable, and the attack must be a chop.
|
||||||
|
if (orientationVerticality > 0.707)
|
||||||
|
swingType = ESM::Weapon::AT_Chop;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Next check if the swing is more horizontal or vertical. A slash
|
||||||
|
// would be more horizontal.
|
||||||
|
if (swingVerticality > 0.707)
|
||||||
|
swingType = ESM::Weapon::AT_Chop;
|
||||||
|
else
|
||||||
|
swingType = ESM::Weapon::AT_Slash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case SwingState_Cooldown:
|
||||||
|
return update_cooldownState();
|
||||||
|
case SwingState_Ready:
|
||||||
|
return update_readyState();
|
||||||
|
case SwingState_Swing:
|
||||||
|
return update_swingState();
|
||||||
|
case SwingState_Impact:
|
||||||
|
return update_impactState();
|
||||||
|
case SwingState_Launch:
|
||||||
|
return update_launchState();
|
||||||
|
default:
|
||||||
|
throw std::logic_error(std::string("You forgot to implement state ") + stateToString(state) + " ya dingus");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::update_cooldownState()
|
||||||
|
{
|
||||||
|
if (timeSinceEnteredState >= minimumPeriod)
|
||||||
|
transition_cooldownToReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::transition_cooldownToReady()
|
||||||
|
{
|
||||||
|
transition(SwingState_Ready);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::update_readyState()
|
||||||
|
{
|
||||||
|
if (canSwing())
|
||||||
|
return transition_readyToLaunch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::transition_readyToLaunch()
|
||||||
|
{
|
||||||
|
transition(SwingState_Launch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::playSwish()
|
||||||
|
{
|
||||||
|
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
|
||||||
|
|
||||||
|
std::string sound = "Weapon Swish";
|
||||||
|
if (strength < 0.5f)
|
||||||
|
sndMgr->playSound3D(ptr, sound, 1.0f, 0.8f); //Weak attack
|
||||||
|
if (strength < 1.0f)
|
||||||
|
sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f); //Medium attack
|
||||||
|
else
|
||||||
|
sndMgr->playSound3D(ptr, sound, 1.0f, 1.2f); //Strong attack
|
||||||
|
|
||||||
|
Log(Debug::Verbose) << "Swing: " << swingTypeToString(swingType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::update_launchState()
|
||||||
|
{
|
||||||
|
if (movementSinceEnteredState > minimumPeriod)
|
||||||
|
transition_launchToSwing();
|
||||||
|
if (!canSwing())
|
||||||
|
return transition_launchToReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::transition_launchToReady()
|
||||||
|
{
|
||||||
|
transition(SwingState_Ready);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::transition_launchToSwing()
|
||||||
|
{
|
||||||
|
playSwish();
|
||||||
|
transition(SwingState_Swing);
|
||||||
|
|
||||||
|
// As a special case, update the new state immediately to allow
|
||||||
|
// same-frame impacts.
|
||||||
|
update_swingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::update_swingState()
|
||||||
|
{
|
||||||
|
maxSwingVelocity = std::max(velocity, maxSwingVelocity);
|
||||||
|
strength = std::min(1.f, (maxSwingVelocity - minVelocity) / maxVelocity);
|
||||||
|
|
||||||
|
// When velocity falls below minimum, transition to register the miss
|
||||||
|
if (!canSwing())
|
||||||
|
return transition_swingingToImpact();
|
||||||
|
// Call hit with simulated=true to check for hit without actually causing an impact
|
||||||
|
if (ptr.getClass().hit(ptr, strength, swingType, true))
|
||||||
|
return transition_swingingToImpact();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::transition_swingingToImpact()
|
||||||
|
{
|
||||||
|
ptr.getClass().hit(ptr, strength, swingType, false);
|
||||||
|
transition(SwingState_Impact);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::update_impactState()
|
||||||
|
{
|
||||||
|
if (velocity < minVelocity)
|
||||||
|
return transition_impactToCooldown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::transition_impactToCooldown()
|
||||||
|
{
|
||||||
|
transition(SwingState_Cooldown);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
switch (state)
|
|
||||||
{
|
|
||||||
case SwingState_Cooldown:
|
|
||||||
return update_cooldownState();
|
|
||||||
case SwingState_Ready:
|
|
||||||
return update_readyState();
|
|
||||||
case SwingState_Swing:
|
|
||||||
return update_swingState();
|
|
||||||
case SwingState_Impact:
|
|
||||||
return update_impactState();
|
|
||||||
case SwingState_Launch:
|
|
||||||
return update_launchState();
|
|
||||||
default:
|
|
||||||
throw std::logic_error(std::string("You forgot to implement state ") + stateToString(state) + " ya dingus");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateMachine::update_cooldownState()
|
|
||||||
{
|
|
||||||
if (timeSinceEnteredState >= minimumPeriod)
|
|
||||||
transition_cooldownToReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
void StateMachine::transition_cooldownToReady()
|
|
||||||
{
|
|
||||||
transition(SwingState_Ready);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StateMachine::update_readyState()
|
|
||||||
{
|
|
||||||
if (canSwing())
|
|
||||||
return transition_readyToLaunch();
|
|
||||||
}
|
|
||||||
|
|
||||||
void StateMachine::transition_readyToLaunch()
|
|
||||||
{
|
|
||||||
transition(SwingState_Launch);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StateMachine::playSwish()
|
|
||||||
{
|
|
||||||
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
|
|
||||||
|
|
||||||
std::string sound = "Weapon Swish";
|
|
||||||
if (strength < 0.5f)
|
|
||||||
sndMgr->playSound3D(ptr, sound, 1.0f, 0.8f); //Weak attack
|
|
||||||
if (strength < 1.0f)
|
|
||||||
sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f); //Medium attack
|
|
||||||
else
|
|
||||||
sndMgr->playSound3D(ptr, sound, 1.0f, 1.2f); //Strong attack
|
|
||||||
|
|
||||||
Log(Debug::Verbose) << "Swing: " << swingTypeToString(swingType);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StateMachine::update_launchState()
|
|
||||||
{
|
|
||||||
if (movementSinceEnteredState > minimumPeriod)
|
|
||||||
transition_launchToSwing();
|
|
||||||
if (!canSwing())
|
|
||||||
return transition_launchToReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
void StateMachine::transition_launchToReady()
|
|
||||||
{
|
|
||||||
transition(SwingState_Ready);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StateMachine::transition_launchToSwing()
|
|
||||||
{
|
|
||||||
playSwish();
|
|
||||||
transition(SwingState_Swing);
|
|
||||||
|
|
||||||
// As a special case, update the new state immediately to allow
|
|
||||||
// same-frame impacts.
|
|
||||||
update_swingState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void StateMachine::update_swingState()
|
|
||||||
{
|
|
||||||
maxSwingVelocity = std::max(velocity, maxSwingVelocity);
|
|
||||||
strength = std::min(1.f, (maxSwingVelocity - minVelocity) / maxVelocity);
|
|
||||||
|
|
||||||
// When velocity falls below minimum, transition to register the miss
|
|
||||||
if (!canSwing())
|
|
||||||
return transition_swingingToImpact();
|
|
||||||
// Call hit with simulated=true to check for hit without actually causing an impact
|
|
||||||
if(ptr.getClass().hit(ptr, strength, swingType, true))
|
|
||||||
return transition_swingingToImpact();
|
|
||||||
}
|
|
||||||
|
|
||||||
void StateMachine::transition_swingingToImpact()
|
|
||||||
{
|
|
||||||
ptr.getClass().hit(ptr, strength, swingType, false);
|
|
||||||
transition(SwingState_Impact);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StateMachine::update_impactState()
|
|
||||||
{
|
|
||||||
if (velocity < minVelocity)
|
|
||||||
return transition_impactToCooldown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void StateMachine::transition_impactToCooldown()
|
|
||||||
{
|
|
||||||
transition(SwingState_Cooldown);
|
|
||||||
}
|
|
||||||
|
|
||||||
}}
|
|
||||||
|
|
|
@ -10,71 +10,73 @@
|
||||||
#include "vrenvironment.hpp"
|
#include "vrenvironment.hpp"
|
||||||
#include "vrsession.hpp"
|
#include "vrsession.hpp"
|
||||||
|
|
||||||
namespace MWVR { namespace RealisticCombat {
|
namespace MWVR {
|
||||||
enum SwingState
|
namespace RealisticCombat {
|
||||||
{
|
enum SwingState
|
||||||
SwingState_Ready,
|
{
|
||||||
SwingState_Launch,
|
SwingState_Ready,
|
||||||
SwingState_Swing,
|
SwingState_Launch,
|
||||||
SwingState_Impact,
|
SwingState_Swing,
|
||||||
SwingState_Cooldown,
|
SwingState_Impact,
|
||||||
};
|
SwingState_Cooldown,
|
||||||
|
};
|
||||||
|
|
||||||
struct StateMachine
|
struct StateMachine
|
||||||
{
|
{
|
||||||
// TODO: These should be configurable
|
// TODO: These should be configurable
|
||||||
const float minVelocity = 1.f;
|
const float minVelocity = 1.f;
|
||||||
const float maxVelocity = 4.f;
|
const float maxVelocity = 4.f;
|
||||||
|
|
||||||
float velocity = 0.f;
|
float velocity = 0.f;
|
||||||
float maxSwingVelocity = 0.f;
|
float maxSwingVelocity = 0.f;
|
||||||
|
|
||||||
SwingState state = SwingState_Ready;
|
SwingState state = SwingState_Ready;
|
||||||
MWWorld::Ptr ptr = MWWorld::Ptr();
|
MWWorld::Ptr ptr = MWWorld::Ptr();
|
||||||
int swingType = -1;
|
int swingType = -1;
|
||||||
float strength = 0.f;
|
float strength = 0.f;
|
||||||
|
|
||||||
float thrustVelocity{ 0.f };
|
float thrustVelocity{ 0.f };
|
||||||
float slashChopVelocity{ 0.f };
|
float slashChopVelocity{ 0.f };
|
||||||
float sideVelocity{ 0.f };
|
float sideVelocity{ 0.f };
|
||||||
|
|
||||||
float minimumPeriod{ .25f };
|
float minimumPeriod{ .25f };
|
||||||
|
|
||||||
float timeSinceEnteredState = { 0.f };
|
float timeSinceEnteredState = { 0.f };
|
||||||
float movementSinceEnteredState = { 0.f };
|
float movementSinceEnteredState = { 0.f };
|
||||||
|
|
||||||
bool mEnabled = false;
|
bool mEnabled = false;
|
||||||
|
|
||||||
osg::Vec3 previousPosition{ 0.f,0.f,0.f };
|
osg::Vec3 previousPosition{ 0.f,0.f,0.f };
|
||||||
|
|
||||||
StateMachine(MWWorld::Ptr ptr);
|
StateMachine(MWWorld::Ptr ptr);
|
||||||
|
|
||||||
bool canSwing();
|
bool canSwing();
|
||||||
|
|
||||||
void playSwish();
|
void playSwish();
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
void transition(SwingState newState);
|
void transition(SwingState newState);
|
||||||
|
|
||||||
void update(float dt, bool enabled);
|
void update(float dt, bool enabled);
|
||||||
|
|
||||||
void update_cooldownState();
|
void update_cooldownState();
|
||||||
void transition_cooldownToReady();
|
void transition_cooldownToReady();
|
||||||
|
|
||||||
void update_readyState();
|
void update_readyState();
|
||||||
void transition_readyToLaunch();
|
void transition_readyToLaunch();
|
||||||
|
|
||||||
void update_launchState();
|
void update_launchState();
|
||||||
void transition_launchToReady();
|
void transition_launchToReady();
|
||||||
void transition_launchToSwing();
|
void transition_launchToSwing();
|
||||||
|
|
||||||
void update_swingState();
|
void update_swingState();
|
||||||
void transition_swingingToImpact();
|
void transition_swingingToImpact();
|
||||||
|
|
||||||
void update_impactState();
|
void update_impactState();
|
||||||
void transition_impactToCooldown();
|
void transition_impactToCooldown();
|
||||||
};
|
};
|
||||||
|
|
||||||
}}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -9,86 +9,91 @@
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
|
||||||
class HandController;
|
class HandController;
|
||||||
class FingerController;
|
class FingerController;
|
||||||
class ForearmController;
|
class ForearmController;
|
||||||
|
|
||||||
/// Subclassing NpcAnimation to override behaviours not compatible with VR
|
/// Subclassing NpcAnimation to override behaviours not compatible with VR
|
||||||
class VRAnimation : public MWRender::NpcAnimation
|
class VRAnimation : public MWRender::NpcAnimation
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
virtual void addControllers();
|
virtual void addControllers();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @param ptr
|
* @param ptr
|
||||||
* @param disableListener Don't listen for equipment changes and magic effects. InventoryStore only supports
|
* @param disableListener Don't listen for equipment changes and magic effects. InventoryStore only supports
|
||||||
* one listener at a time, so you shouldn't do this if creating several NpcAnimations
|
* one listener at a time, so you shouldn't do this if creating several NpcAnimations
|
||||||
* for the same Ptr, eg preview dolls for the player.
|
* for the same Ptr, eg preview dolls for the player.
|
||||||
* Those need to be manually rendered anyway.
|
* Those need to be manually rendered anyway.
|
||||||
* @param disableSounds Same as \a disableListener but for playing items sounds
|
* @param disableSounds Same as \a disableListener but for playing items sounds
|
||||||
* @param xrSession The XR session that shall be used to track limbs
|
* @param xrSession The XR session that shall be used to track limbs
|
||||||
*/
|
*/
|
||||||
VRAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> parentNode, Resource::ResourceSystem* resourceSystem,
|
VRAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> parentNode, Resource::ResourceSystem* resourceSystem,
|
||||||
bool disableSounds, std::shared_ptr<VRSession> xrSession );
|
bool disableSounds, std::shared_ptr<VRSession> xrSession);
|
||||||
virtual ~VRAnimation();
|
virtual ~VRAnimation();
|
||||||
|
|
||||||
/// Overridden to always be false
|
/// Overridden to always be false
|
||||||
virtual void enableHeadAnimation(bool enable);
|
virtual void enableHeadAnimation(bool enable);
|
||||||
|
|
||||||
/// Overridden to always be false
|
/// Overridden to always be false
|
||||||
virtual void setAccurateAiming(bool enabled);
|
virtual void setAccurateAiming(bool enabled);
|
||||||
|
|
||||||
/// Overridden, implementation tbd
|
/// Overridden, implementation tbd
|
||||||
virtual osg::Vec3f runAnimation(float timepassed);
|
virtual osg::Vec3f runAnimation(float timepassed);
|
||||||
|
|
||||||
/// A relative factor (0-1) that decides if and how much the skeleton should be pitched
|
/// A relative factor (0-1) that decides if and how much the skeleton should be pitched
|
||||||
/// to indicate the facing orientation of the character.
|
/// to indicate the facing orientation of the character.
|
||||||
virtual void setPitchFactor(float factor) { mPitchFactor = factor; }
|
virtual void setPitchFactor(float factor) { mPitchFactor = factor; }
|
||||||
|
|
||||||
/// Overriden to always be a variant of VM_VR*
|
/// Overriden to always be a variant of VM_VR*
|
||||||
virtual void setViewMode(ViewMode viewMode);
|
virtual void setViewMode(ViewMode viewMode);
|
||||||
|
|
||||||
/// Overriden to include VR modifications
|
/// Overriden to include VR modifications
|
||||||
virtual void updateParts();
|
virtual void updateParts();
|
||||||
|
|
||||||
/// Overrides finger animations to point forward
|
/// Overrides finger animations to point forward
|
||||||
void setFingerPointingMode(bool enabled);
|
void setFingerPointingMode(bool enabled);
|
||||||
bool fingerPointingMode() const { return mFingerPointingMode; }
|
|
||||||
|
|
||||||
bool canPlaceObject();
|
/// @return Whether animation is currently in finger pointing mode
|
||||||
///< @return true if it is possible to place on object where the player is currently pointing
|
bool fingerPointingMode() const { return mFingerPointingMode; }
|
||||||
|
|
||||||
const MWRender::RayResult& getPointerTarget() const;
|
/// @return true if it is possible to place on object where the player is currently pointing
|
||||||
///< @return pointer to the object the player's melee weapon is currently intersecting.
|
bool canPlaceObject();
|
||||||
|
|
||||||
void updatePointerTarget();
|
/// @return pointer to the object the player's melee weapon is currently intersecting.
|
||||||
|
const MWRender::RayResult& getPointerTarget() const;
|
||||||
|
|
||||||
MWWorld::Ptr getTarget(const std::string& directorNode);
|
/// Update what object this vr animation is currently pointing at.
|
||||||
|
void updatePointerTarget();
|
||||||
|
|
||||||
osg::Matrix getWeaponTransformMatrix() const;
|
/// @return whatever ref is the current pointer target, if any
|
||||||
|
MWWorld::Ptr getTarget(const std::string& directorNode);
|
||||||
|
|
||||||
protected:
|
/// @return world transform that yields the position and orientation of the current weapon
|
||||||
static osg::ref_ptr<osg::Geometry> createPointerGeometry(void);
|
osg::Matrix getWeaponTransformMatrix() const;
|
||||||
|
|
||||||
float getVelocity(const std::string& groupname) const override;
|
protected:
|
||||||
|
static osg::ref_ptr<osg::Geometry> createPointerGeometry(void);
|
||||||
|
|
||||||
protected:
|
float getVelocity(const std::string& groupname) const override;
|
||||||
std::shared_ptr<VRSession> mSession;
|
|
||||||
osg::ref_ptr<ForearmController> mForearmControllers[2];
|
|
||||||
osg::ref_ptr<HandController> mHandControllers[2];
|
|
||||||
osg::ref_ptr<FingerController> mIndexFingerControllers[2];
|
|
||||||
osg::ref_ptr<osg::MatrixTransform> mModelOffset;
|
|
||||||
|
|
||||||
bool mFingerPointingMode{ false };
|
protected:
|
||||||
osg::ref_ptr<osg::Geometry> mPointerGeometry{ nullptr };
|
std::shared_ptr<VRSession> mSession;
|
||||||
osg::ref_ptr<osg::MatrixTransform> mPointerRescale{ nullptr };
|
osg::ref_ptr<ForearmController> mForearmControllers[2];
|
||||||
osg::ref_ptr<osg::MatrixTransform> mPointerTransform{ nullptr };
|
osg::ref_ptr<HandController> mHandControllers[2];
|
||||||
osg::ref_ptr<osg::MatrixTransform> mWeaponDirectionTransform{ nullptr };
|
osg::ref_ptr<FingerController> mIndexFingerControllers[2];
|
||||||
osg::ref_ptr<osg::MatrixTransform> mWeaponPointerTransform{ nullptr };
|
osg::ref_ptr<osg::MatrixTransform> mModelOffset;
|
||||||
MWRender::RayResult mPointerTarget{};
|
|
||||||
float mDistanceToPointerTarget{ -1.f };
|
bool mFingerPointingMode{ false };
|
||||||
};
|
osg::ref_ptr<osg::Geometry> mPointerGeometry{ nullptr };
|
||||||
|
osg::ref_ptr<osg::MatrixTransform> mPointerRescale{ nullptr };
|
||||||
|
osg::ref_ptr<osg::MatrixTransform> mPointerTransform{ nullptr };
|
||||||
|
osg::ref_ptr<osg::MatrixTransform> mWeaponDirectionTransform{ nullptr };
|
||||||
|
osg::ref_ptr<osg::MatrixTransform> mWeaponPointerTransform{ nullptr };
|
||||||
|
MWRender::RayResult mPointerTarget{};
|
||||||
|
float mDistanceToPointerTarget{ -1.f };
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,12 @@
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
|
||||||
MWVR::Environment *MWVR::Environment::sThis = 0;
|
MWVR::Environment* MWVR::Environment::sThis = 0;
|
||||||
|
|
||||||
MWVR::Environment::Environment()
|
MWVR::Environment::Environment()
|
||||||
: mSession(nullptr)
|
: mSession(nullptr)
|
||||||
{
|
{
|
||||||
assert (!sThis);
|
assert(!sThis);
|
||||||
sThis = this;
|
sThis = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,15 +42,15 @@ void MWVR::Environment::cleanup()
|
||||||
|
|
||||||
MWVR::Environment& MWVR::Environment::get()
|
MWVR::Environment& MWVR::Environment::get()
|
||||||
{
|
{
|
||||||
assert (sThis);
|
assert(sThis);
|
||||||
return *sThis;
|
return *sThis;
|
||||||
}
|
}
|
||||||
|
|
||||||
MWVR::OpenXRInputManager* MWVR::Environment::getInputManager() const
|
MWVR::VRInputManager* MWVR::Environment::getInputManager() const
|
||||||
{
|
{
|
||||||
auto* inputManager = MWBase::Environment::get().getInputManager();
|
auto* inputManager = MWBase::Environment::get().getInputManager();
|
||||||
assert(inputManager);
|
assert(inputManager);
|
||||||
auto xrInputManager = dynamic_cast<MWVR::OpenXRInputManager*>(inputManager);
|
auto xrInputManager = dynamic_cast<MWVR::VRInputManager*>(inputManager);
|
||||||
assert(xrInputManager);
|
assert(xrInputManager);
|
||||||
return xrInputManager;
|
return xrInputManager;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,15 @@
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
class VRAnimation;
|
class VRAnimation;
|
||||||
class OpenXRInputManager;
|
class VRInputManager;
|
||||||
class VRSession;
|
class VRSession;
|
||||||
class VRGUIManager;
|
class VRGUIManager;
|
||||||
class VRViewer;
|
class VRViewer;
|
||||||
class OpenXRManager;
|
class OpenXRManager;
|
||||||
|
|
||||||
/// \brief Central hub for mw openxr subsystems
|
/// \brief Central hub for mw vr/openxr subsystems
|
||||||
///
|
///
|
||||||
/// This class allows each mw openxr subsystem to access any others subsystem's top-level manager class.
|
/// This class allows each mw subsystem to access any vr subsystem's top-level manager class.
|
||||||
///
|
///
|
||||||
/// \attention Environment takes ownership of the manager class instances it is handed over in
|
/// \attention Environment takes ownership of the manager class instances it is handed over in
|
||||||
/// the set* functions.
|
/// the set* functions.
|
||||||
|
@ -39,7 +39,7 @@ namespace MWVR
|
||||||
static Environment& get();
|
static Environment& get();
|
||||||
///< Return instance of this class.
|
///< Return instance of this class.
|
||||||
|
|
||||||
MWVR::OpenXRInputManager* getInputManager() const;
|
MWVR::VRInputManager* getInputManager() const;
|
||||||
|
|
||||||
// The OpenXRInputManager supplants the regular input manager
|
// The OpenXRInputManager supplants the regular input manager
|
||||||
// which is stored in MWBase::Environment
|
// which is stored in MWBase::Environment
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,14 +17,14 @@
|
||||||
|
|
||||||
namespace MyGUI
|
namespace MyGUI
|
||||||
{
|
{
|
||||||
class Widget;
|
class Widget;
|
||||||
class Window;
|
class Window;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
class Layout;
|
class Layout;
|
||||||
class WindowBase;
|
class WindowBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct XrCompositionLayerQuad;
|
struct XrCompositionLayerQuad;
|
||||||
|
|
|
@ -10,134 +10,134 @@
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
|
||||||
//! Delay before a long-press action is activated (and regular press is discarded)
|
//! Delay before a long-press action is activated (and regular press is discarded)
|
||||||
//! TODO: Make this configurable?
|
//! TODO: Make this configurable?
|
||||||
static std::chrono::milliseconds gActionTime{ 666 };
|
static std::chrono::milliseconds gActionTime{ 666 };
|
||||||
//! Magnitude above which an axis action is considered active
|
//! Magnitude above which an axis action is considered active
|
||||||
static float gAxisEpsilon{ 0.01f };
|
static float gAxisEpsilon{ 0.01f };
|
||||||
|
|
||||||
void HapticsAction::apply(float amplitude)
|
void HapticsAction::apply(float amplitude)
|
||||||
{
|
|
||||||
mAmplitude = std::max(0.f, std::min(1.f, amplitude));
|
|
||||||
mXRAction->applyHaptics(XR_NULL_PATH, mAmplitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
PoseAction::PoseAction(std::unique_ptr<OpenXRAction> xrAction)
|
|
||||||
: mXRAction(std::move(xrAction))
|
|
||||||
, mXRSpace{ XR_NULL_HANDLE }
|
|
||||||
{
|
|
||||||
auto* xr = Environment::get().getManager();
|
|
||||||
XrActionSpaceCreateInfo createInfo{ XR_TYPE_ACTION_SPACE_CREATE_INFO };
|
|
||||||
createInfo.action = *mXRAction;
|
|
||||||
createInfo.poseInActionSpace.orientation.w = 1.f;
|
|
||||||
createInfo.subactionPath = XR_NULL_PATH;
|
|
||||||
CHECK_XRCMD(xrCreateActionSpace(xr->impl().xrSession(), &createInfo, &mXRSpace));
|
|
||||||
}
|
|
||||||
|
|
||||||
void PoseAction::update(long long time)
|
|
||||||
{
|
|
||||||
mPrevious = mValue;
|
|
||||||
|
|
||||||
auto* xr = Environment::get().getManager();
|
|
||||||
XrSpace referenceSpace = xr->impl().getReferenceSpace(ReferenceSpace::STAGE);
|
|
||||||
|
|
||||||
XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION };
|
|
||||||
XrSpaceVelocity velocity{ XR_TYPE_SPACE_VELOCITY };
|
|
||||||
location.next = &velocity;
|
|
||||||
CHECK_XRCMD(xrLocateSpace(mXRSpace, 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 a valid quat so osg won't throw errors
|
|
||||||
location.pose.orientation.w = 1;
|
|
||||||
|
|
||||||
mValue = Pose{
|
|
||||||
fromXR(location.pose.position),
|
|
||||||
fromXR(location.pose.orientation)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void Action::updateAndQueue(std::deque<const Action*>& queue)
|
|
||||||
{
|
|
||||||
bool old = mActive;
|
|
||||||
mPrevious = mValue;
|
|
||||||
update();
|
|
||||||
bool changed = old != mActive;
|
|
||||||
mOnActivate = changed && mActive;
|
|
||||||
mOnDeactivate = changed && !mActive;
|
|
||||||
|
|
||||||
if (shouldQueue())
|
|
||||||
{
|
{
|
||||||
queue.push_back(this);
|
mAmplitude = std::max(0.f, std::min(1.f, amplitude));
|
||||||
|
mXRAction->applyHaptics(XR_NULL_PATH, mAmplitude);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void ButtonPressAction::update()
|
PoseAction::PoseAction(std::unique_ptr<OpenXRAction> xrAction)
|
||||||
{
|
: mXRAction(std::move(xrAction))
|
||||||
mActive = false;
|
, mXRSpace{ XR_NULL_HANDLE }
|
||||||
bool old = mPressed;
|
|
||||||
mXRAction->getBool(0, mPressed);
|
|
||||||
bool changed = old != mPressed;
|
|
||||||
if (changed && mPressed)
|
|
||||||
{
|
{
|
||||||
mPressTime = std::chrono::steady_clock::now();
|
auto* xr = Environment::get().getManager();
|
||||||
mTimeout = mPressTime + gActionTime;
|
XrActionSpaceCreateInfo createInfo{ XR_TYPE_ACTION_SPACE_CREATE_INFO };
|
||||||
|
createInfo.action = *mXRAction;
|
||||||
|
createInfo.poseInActionSpace.orientation.w = 1.f;
|
||||||
|
createInfo.subactionPath = XR_NULL_PATH;
|
||||||
|
CHECK_XRCMD(xrCreateActionSpace(xr->impl().xrSession(), &createInfo, &mXRSpace));
|
||||||
}
|
}
|
||||||
if (changed && !mPressed)
|
|
||||||
|
void PoseAction::update(long long time)
|
||||||
{
|
{
|
||||||
if (std::chrono::steady_clock::now() < mTimeout)
|
mPrevious = mValue;
|
||||||
|
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
XrSpace referenceSpace = xr->impl().getReferenceSpace(ReferenceSpace::STAGE);
|
||||||
|
|
||||||
|
XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION };
|
||||||
|
XrSpaceVelocity velocity{ XR_TYPE_SPACE_VELOCITY };
|
||||||
|
location.next = &velocity;
|
||||||
|
CHECK_XRCMD(xrLocateSpace(mXRSpace, 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 a valid quat so osg won't throw errors
|
||||||
|
location.pose.orientation.w = 1;
|
||||||
|
|
||||||
|
mValue = Pose{
|
||||||
|
fromXR(location.pose.position),
|
||||||
|
fromXR(location.pose.orientation)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::updateAndQueue(std::deque<const Action*>& queue)
|
||||||
|
{
|
||||||
|
bool old = mActive;
|
||||||
|
mPrevious = mValue;
|
||||||
|
update();
|
||||||
|
bool changed = old != mActive;
|
||||||
|
mOnActivate = changed && mActive;
|
||||||
|
mOnDeactivate = changed && !mActive;
|
||||||
|
|
||||||
|
if (shouldQueue())
|
||||||
{
|
{
|
||||||
|
queue.push_back(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonPressAction::update()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mValue = mPressed ? 1.f : 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonLongPressAction::update()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ButtonHoldAction::update()
|
||||||
|
{
|
||||||
|
mXRAction->getBool(0, mPressed);
|
||||||
|
mActive = mPressed;
|
||||||
|
|
||||||
|
mValue = mPressed ? 1.f : 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AxisAction::update()
|
||||||
|
{
|
||||||
|
mActive = false;
|
||||||
|
mXRAction->getFloat(0, mValue);
|
||||||
|
|
||||||
|
if (std::fabs(mValue) > gAxisEpsilon)
|
||||||
mActive = true;
|
mActive = true;
|
||||||
}
|
else
|
||||||
|
mValue = 0.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
mValue = mPressed ? 1.f : 0.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ButtonLongPressAction::update()
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ButtonHoldAction::update()
|
|
||||||
{
|
|
||||||
mXRAction->getBool(0, mPressed);
|
|
||||||
mActive = mPressed;
|
|
||||||
|
|
||||||
mValue = mPressed ? 1.f : 0.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void AxisAction::update()
|
|
||||||
{
|
|
||||||
mActive = false;
|
|
||||||
mXRAction->getFloat(0, mValue);
|
|
||||||
|
|
||||||
if (std::fabs(mValue) > gAxisEpsilon)
|
|
||||||
mActive = true;
|
|
||||||
else
|
|
||||||
mValue = 0.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,207 +10,218 @@ namespace MWVR
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
// The OpenMW input manager iterates from 0 to A_Last in its actions enum.
|
/// Extension of MWInput's set of actions.
|
||||||
// I don't know that it would cause any ill effects, but i nonetheless do not
|
enum VrActions
|
||||||
// want to contaminate the input manager with my OpenXR specific actions.
|
{
|
||||||
// Therefore i add them here to a separate enum whose values start past A_Last.
|
A_VrFirst = MWInput::A_Last + 1,
|
||||||
enum VrActions
|
A_ActivateTouch,
|
||||||
{
|
A_HapticsLeft,
|
||||||
A_VrFirst = MWInput::A_Last + 1,
|
A_HapticsRight,
|
||||||
A_ActivateTouch,
|
A_HandPoseLeft,
|
||||||
A_HapticsLeft,
|
A_HandPoseRight,
|
||||||
A_HapticsRight,
|
A_MenuUpDown,
|
||||||
A_HandPoseLeft,
|
A_MenuLeftRight,
|
||||||
A_HandPoseRight,
|
A_MenuSelect,
|
||||||
A_MenuUpDown,
|
A_MenuBack,
|
||||||
A_MenuLeftRight,
|
A_Recenter,
|
||||||
A_MenuSelect,
|
A_VrLast
|
||||||
A_MenuBack,
|
};
|
||||||
A_Recenter,
|
|
||||||
A_VrLast
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ActionPath
|
/// \brief Enum representation of action paths in openxr.
|
||||||
{
|
///
|
||||||
Pose = 0,
|
/// OpenXR allows a lot of generics, consequentially this will most likely be changed/expanded
|
||||||
Haptic,
|
/// in the future as we need it. This set was added based on what the Oculus needed.
|
||||||
Menu,
|
enum class ActionPath
|
||||||
ThumbstickX,
|
{
|
||||||
ThumbstickY,
|
Pose = 0,
|
||||||
ThumbstickClick,
|
Haptic,
|
||||||
Select,
|
Menu,
|
||||||
Squeeze,
|
ThumbstickX,
|
||||||
Trigger,
|
ThumbstickY,
|
||||||
X,
|
ThumbstickClick,
|
||||||
Y,
|
Select,
|
||||||
A,
|
Squeeze,
|
||||||
B,
|
Trigger,
|
||||||
Last
|
X,
|
||||||
};
|
Y,
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
Last
|
||||||
|
};
|
||||||
|
|
||||||
//class OpenXRAction;
|
/// \brief Suggest a binding by binding an action to a path on a given hand (left or right).
|
||||||
|
struct SuggestedBindings
|
||||||
|
{
|
||||||
|
struct Binding
|
||||||
|
{
|
||||||
|
int action;
|
||||||
|
ActionPath path;
|
||||||
|
Side side;
|
||||||
|
};
|
||||||
|
|
||||||
//! Action for applying haptics
|
std::string controllerPath;
|
||||||
class HapticsAction
|
std::vector<Binding> bindings;
|
||||||
{
|
};
|
||||||
public:
|
|
||||||
HapticsAction(std::unique_ptr<OpenXRAction> xrAction) : mXRAction{ std::move(xrAction) } {};
|
|
||||||
|
|
||||||
//! Apply vibration at the given amplitude
|
/// \brief Action for applying haptics
|
||||||
void apply(float amplitude);
|
class HapticsAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HapticsAction(std::unique_ptr<OpenXRAction> xrAction) : mXRAction{ std::move(xrAction) } {};
|
||||||
|
|
||||||
//! Convenience
|
//! Apply vibration at the given amplitude
|
||||||
operator XrAction() { return *mXRAction; }
|
void apply(float amplitude);
|
||||||
|
|
||||||
private:
|
//! Convenience
|
||||||
std::unique_ptr<OpenXRAction> mXRAction;
|
operator XrAction() { return *mXRAction; }
|
||||||
float mAmplitude{ 0.f };
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Action for capturing tracking information
|
private:
|
||||||
class PoseAction
|
std::unique_ptr<OpenXRAction> mXRAction;
|
||||||
{
|
float mAmplitude{ 0.f };
|
||||||
public:
|
};
|
||||||
PoseAction(std::unique_ptr<OpenXRAction> xrAction);
|
|
||||||
|
|
||||||
//! Current value of an axis or lever action
|
/// \brief Action for capturing tracking information
|
||||||
Pose value() const { return mValue; }
|
class PoseAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PoseAction(std::unique_ptr<OpenXRAction> xrAction);
|
||||||
|
|
||||||
//! Previous value
|
//! Current value of an axis or lever action
|
||||||
Pose previousValue() const { return mPrevious; }
|
Pose value() const { return mValue; }
|
||||||
|
|
||||||
//! Convenience
|
//! Previous value
|
||||||
operator XrAction() { return *mXRAction; }
|
Pose previousValue() const { return mPrevious; }
|
||||||
|
|
||||||
//! Update pose value
|
//! Convenience
|
||||||
void update(long long time);
|
operator XrAction() { return *mXRAction; }
|
||||||
|
|
||||||
private:
|
//! Update pose value
|
||||||
std::unique_ptr<OpenXRAction> mXRAction;
|
void update(long long time);
|
||||||
XrSpace mXRSpace;
|
|
||||||
Pose mValue{};
|
|
||||||
Pose mPrevious{};
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Generic action
|
private:
|
||||||
class Action
|
std::unique_ptr<OpenXRAction> mXRAction;
|
||||||
{
|
XrSpace mXRSpace;
|
||||||
public:
|
Pose mValue{};
|
||||||
Action(int openMWAction, std::unique_ptr<OpenXRAction> xrAction) : mXRAction(std::move(xrAction)), mOpenMWAction(openMWAction) {}
|
Pose mPrevious{};
|
||||||
virtual ~Action() {};
|
};
|
||||||
|
|
||||||
//! True if action changed to being released in the last update
|
/// \brief Generic action
|
||||||
bool isActive() const { return mActive; };
|
/// \sa ButtonPressAction ButtonLongPressAction ButtonHoldAction AxisAction
|
||||||
|
class Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Action(int openMWAction, std::unique_ptr<OpenXRAction> xrAction) : mXRAction(std::move(xrAction)), mOpenMWAction(openMWAction) {}
|
||||||
|
virtual ~Action() {};
|
||||||
|
|
||||||
//! True if activation turned on this update (i.e. onPress)
|
//! True if action changed to being released in the last update
|
||||||
bool onActivate() const { return mOnActivate; }
|
bool isActive() const { return mActive; };
|
||||||
|
|
||||||
//! True if activation turned off this update (i.e. onRelease)
|
//! True if activation turned on this update (i.e. onPress)
|
||||||
bool onDeactivate() const { return mOnDeactivate; }
|
bool onActivate() const { return mOnActivate; }
|
||||||
|
|
||||||
//! OpenMW Action code of this action
|
//! True if activation turned off this update (i.e. onRelease)
|
||||||
int openMWActionCode() const { return mOpenMWAction; }
|
bool onDeactivate() const { return mOnDeactivate; }
|
||||||
|
|
||||||
//! Current value of an axis or lever action
|
//! OpenMW Action code of this action
|
||||||
float value() const { return mValue; }
|
int openMWActionCode() const { return mOpenMWAction; }
|
||||||
|
|
||||||
//! Previous value
|
//! Current value of an axis or lever action
|
||||||
float previousValue() const { return mPrevious; }
|
float value() const { return mValue; }
|
||||||
|
|
||||||
//! Update internal states. Note that subclasses maintain both mValue and mActivate to allow
|
//! Previous value
|
||||||
//! axis and press to subtitute one another.
|
float previousValue() const { return mPrevious; }
|
||||||
virtual void update() = 0;
|
|
||||||
|
|
||||||
//! Determines if an action update should be queued
|
//! Update internal states. Note that subclasses maintain both mValue and mActivate to allow
|
||||||
virtual bool shouldQueue() const = 0;
|
//! axis and press to subtitute one another.
|
||||||
|
virtual void update() = 0;
|
||||||
|
|
||||||
//! Convenience
|
//! Determines if an action update should be queued
|
||||||
operator XrAction() { return *mXRAction; }
|
virtual bool shouldQueue() const = 0;
|
||||||
|
|
||||||
//! Update and queue action if applicable
|
//! Convenience
|
||||||
void updateAndQueue(std::deque<const Action*>& queue);
|
operator XrAction() { return *mXRAction; }
|
||||||
|
|
||||||
protected:
|
//! Update and queue action if applicable
|
||||||
|
void updateAndQueue(std::deque<const Action*>& queue);
|
||||||
|
|
||||||
std::unique_ptr<OpenXRAction> mXRAction;
|
protected:
|
||||||
int mOpenMWAction;
|
|
||||||
float mValue{ 0.f };
|
|
||||||
float mPrevious{ 0.f };
|
|
||||||
bool mActive{ false };
|
|
||||||
bool mOnActivate{ false };
|
|
||||||
bool mOnDeactivate{ false };
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Convenience
|
std::unique_ptr<OpenXRAction> mXRAction;
|
||||||
using ActionPtr = std::unique_ptr<Action>;
|
int mOpenMWAction;
|
||||||
|
float mValue{ 0.f };
|
||||||
|
float mPrevious{ 0.f };
|
||||||
|
bool mActive{ false };
|
||||||
|
bool mOnActivate{ false };
|
||||||
|
bool mOnDeactivate{ false };
|
||||||
|
};
|
||||||
|
|
||||||
//! Action that activates once on release.
|
//! Action that activates once on release.
|
||||||
//! Times out if the button is held longer than gHoldDelay.
|
//! Times out if the button is held longer than gHoldDelay.
|
||||||
class ButtonPressAction : public Action
|
class ButtonPressAction : public Action
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using Action::Action;
|
using Action::Action;
|
||||||
|
|
||||||
static const XrActionType ActionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
|
static const XrActionType ActionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
|
||||||
|
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
virtual bool shouldQueue() const override { return onActivate() || onDeactivate(); }
|
virtual bool shouldQueue() const override { return onActivate() || onDeactivate(); }
|
||||||
|
|
||||||
bool mPressed{ false };
|
bool mPressed{ false };
|
||||||
std::chrono::steady_clock::time_point mPressTime{};
|
std::chrono::steady_clock::time_point mPressTime{};
|
||||||
std::chrono::steady_clock::time_point mTimeout{};
|
std::chrono::steady_clock::time_point mTimeout{};
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Action that activates once on a long press
|
//! 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.
|
//! The press time is the same as the timeout for a regular press, allowing keys with double roles.
|
||||||
class ButtonLongPressAction : public Action
|
class ButtonLongPressAction : public Action
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using Action::Action;
|
using Action::Action;
|
||||||
|
|
||||||
static const XrActionType ActionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
|
static const XrActionType ActionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
|
||||||
|
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
virtual bool shouldQueue() const override { return onActivate() || onDeactivate(); }
|
virtual bool shouldQueue() const override { return onActivate() || onDeactivate(); }
|
||||||
|
|
||||||
bool mPressed{ false };
|
bool mPressed{ false };
|
||||||
bool mActivated{ false };
|
bool mActivated{ false };
|
||||||
std::chrono::steady_clock::time_point mPressTime{};
|
std::chrono::steady_clock::time_point mPressTime{};
|
||||||
std::chrono::steady_clock::time_point mTimein{};
|
std::chrono::steady_clock::time_point mTimein{};
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Action that is active whenever the button is pressed down.
|
//! Action that is active whenever the button is pressed down.
|
||||||
//! Useful for e.g. non-toggling sneak and automatically repeating actions
|
//! Useful for e.g. non-toggling sneak and automatically repeating actions
|
||||||
class ButtonHoldAction : public Action
|
class ButtonHoldAction : public Action
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using Action::Action;
|
using Action::Action;
|
||||||
|
|
||||||
static const XrActionType ActionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
|
static const XrActionType ActionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
|
||||||
|
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
virtual bool shouldQueue() const override { return mActive || onDeactivate(); }
|
virtual bool shouldQueue() const override { return mActive || onDeactivate(); }
|
||||||
|
|
||||||
bool mPressed{ false };
|
bool mPressed{ false };
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Action for axis actions, such as thumbstick axes or certain triggers/squeeze levers.
|
//! 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
|
//! Float axis are considered active whenever their magnitude is greater than gAxisEpsilon. This is useful
|
||||||
//! as a touch subtitute on levers without touch.
|
//! as a touch subtitute on levers without touch.
|
||||||
class AxisAction : public Action
|
class AxisAction : public Action
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using Action::Action;
|
using Action::Action;
|
||||||
|
|
||||||
static const XrActionType ActionType = XR_ACTION_TYPE_FLOAT_INPUT;
|
static const XrActionType ActionType = XR_ACTION_TYPE_FLOAT_INPUT;
|
||||||
|
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
virtual bool shouldQueue() const override { return mActive || onDeactivate(); }
|
virtual bool shouldQueue() const override { return mActive || onDeactivate(); }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -13,69 +13,69 @@
|
||||||
|
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
struct OpenXRInput;
|
struct OpenXRInput;
|
||||||
|
|
||||||
/// As far as I can tell, SDL does not support VR controllers.
|
/// As far as I can tell, SDL does not support VR controllers.
|
||||||
/// So I subclass the input manager and insert VR controls.
|
/// So I subclass the input manager and insert VR controls.
|
||||||
class OpenXRInputManager : public MWInput::InputManager
|
class VRInputManager : public MWInput::InputManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
OpenXRInputManager(
|
VRInputManager(
|
||||||
SDL_Window* window,
|
SDL_Window* window,
|
||||||
osg::ref_ptr<osgViewer::Viewer> viewer,
|
osg::ref_ptr<osgViewer::Viewer> viewer,
|
||||||
osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler,
|
osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler,
|
||||||
osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation,
|
osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation,
|
||||||
const std::string& userFile, bool userFileExists,
|
const std::string& userFile, bool userFileExists,
|
||||||
const std::string& userControllerBindingsFile,
|
const std::string& userControllerBindingsFile,
|
||||||
const std::string& controllerBindingsFile, bool grab);
|
const std::string& controllerBindingsFile, bool grab);
|
||||||
|
|
||||||
virtual ~OpenXRInputManager();
|
virtual ~VRInputManager();
|
||||||
|
|
||||||
/// Overriden to force vr modes such as hiding cursors and crosshairs
|
/// Overriden to force vr modes such as hiding cursors and crosshairs
|
||||||
virtual void changeInputMode(bool guiMode);
|
virtual void changeInputMode(bool guiMode);
|
||||||
|
|
||||||
/// Overriden to update XR inputs
|
/// Overriden to update XR inputs
|
||||||
virtual void update(float dt, bool disableControls = false, bool disableEvents = false);
|
virtual void update(float dt, bool disableControls = false, bool disableEvents = false);
|
||||||
|
|
||||||
/// Current head offset from character position
|
/// Current head offset from character position
|
||||||
osg::Vec3 headOffset() const { return mHeadOffset; };
|
osg::Vec3 headOffset() const { return mHeadOffset; };
|
||||||
|
|
||||||
/// Update head offset. Should only be called by the movement solver when reducing head offset.
|
/// Update head offset. Should only be called by the movement solver when reducing head offset.
|
||||||
void setHeadOffset(osg::Vec3 offset) { mHeadOffset = offset; };
|
void setHeadOffset(osg::Vec3 offset) { mHeadOffset = offset; };
|
||||||
|
|
||||||
/// Quaternion that aligns VR stage coordinates with world coordinates.
|
/// Quaternion that aligns VR stage coordinates with world coordinates.
|
||||||
osg::Quat stageRotation();
|
osg::Quat stageRotation();
|
||||||
|
|
||||||
/// Set current offset to 0 and re-align VR stage.
|
/// Set current offset to 0 and re-align VR stage.
|
||||||
void requestRecenter();
|
void requestRecenter();
|
||||||
|
|
||||||
/// Tracking pose of the given limb at the given predicted time
|
/// Tracking pose of the given limb at the given predicted time
|
||||||
Pose getLimbPose(int64_t time, TrackedLimb limb);
|
Pose getLimbPose(int64_t time, TrackedLimb limb);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void updateHead();
|
void updateHead();
|
||||||
|
|
||||||
void processAction(const class Action* action, float dt, bool disableControls);
|
void processAction(const class Action* action, float dt, bool disableControls);
|
||||||
|
|
||||||
void updateActivationIndication(void);
|
void updateActivationIndication(void);
|
||||||
void pointActivation(bool onPress);
|
void pointActivation(bool onPress);
|
||||||
|
|
||||||
void injectMousePress(int sdlButton, bool onPress);
|
void injectMousePress(int sdlButton, bool onPress);
|
||||||
void injectChannelValue(MWInput::Actions action, float value);
|
void injectChannelValue(MWInput::Actions action, float value);
|
||||||
|
|
||||||
void applyHapticsLeftHand(float intensity) override;
|
void applyHapticsLeftHand(float intensity) override;
|
||||||
void applyHapticsRightHand(float intensity) override;
|
void applyHapticsRightHand(float intensity) override;
|
||||||
|
|
||||||
std::unique_ptr<OpenXRInput> mXRInput;
|
std::unique_ptr<OpenXRInput> mXRInput;
|
||||||
std::unique_ptr<RealisticCombat::StateMachine> mRealisticCombat;
|
std::unique_ptr<RealisticCombat::StateMachine> mRealisticCombat;
|
||||||
Pose mHeadPose{};
|
Pose mHeadPose{};
|
||||||
osg::Vec3 mHeadOffset{ 0,0,0 };
|
osg::Vec3 mHeadOffset{ 0,0,0 };
|
||||||
bool mShouldRecenter{ true };
|
bool mShouldRecenter{ true };
|
||||||
bool mActivationIndication{ false };
|
bool mActivationIndication{ false };
|
||||||
float mYaw{ 0.f };
|
float mYaw{ 0.f };
|
||||||
|
|
||||||
float mVrAngles[3]{ 0.f,0.f,0.f };
|
float mVrAngles[3]{ 0.f,0.f,0.f };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -36,323 +36,323 @@
|
||||||
|
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
VRSession::VRSession()
|
VRSession::VRSession()
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
VRSession::~VRSession()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
osg::Matrix VRSession::projectionMatrix(FramePhase phase, Side side)
|
|
||||||
{
|
|
||||||
assert(((int)side) < 2);
|
|
||||||
auto fov = predictedPoses(VRSession::FramePhase::Update).view[(int)side].fov;
|
|
||||||
float near_ = Settings::Manager::getFloat("near clip", "Camera");
|
|
||||||
float far_ = Settings::Manager::getFloat("viewing distance", "Camera");
|
|
||||||
return fov.perspectiveMatrix(near_, far_);
|
|
||||||
}
|
|
||||||
|
|
||||||
osg::Matrix VRSession::viewMatrix(FramePhase phase, Side side)
|
|
||||||
{
|
|
||||||
MWVR::Pose pose{};
|
|
||||||
pose = predictedPoses(phase).view[(int)side].pose;
|
|
||||||
|
|
||||||
|
|
||||||
if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame)
|
|
||||||
{
|
{
|
||||||
pose = predictedPoses(phase).eye[(int)side];
|
}
|
||||||
|
|
||||||
|
VRSession::~VRSession()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Matrix VRSession::projectionMatrix(FramePhase phase, Side side)
|
||||||
|
{
|
||||||
|
assert(((int)side) < 2);
|
||||||
|
auto fov = predictedPoses(VRSession::FramePhase::Update).view[(int)side].fov;
|
||||||
|
float near_ = Settings::Manager::getFloat("near clip", "Camera");
|
||||||
|
float far_ = Settings::Manager::getFloat("viewing distance", "Camera");
|
||||||
|
return fov.perspectiveMatrix(near_, far_);
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Matrix VRSession::viewMatrix(FramePhase phase, Side side)
|
||||||
|
{
|
||||||
|
MWVR::Pose pose{};
|
||||||
|
pose = predictedPoses(phase).view[(int)side].pose;
|
||||||
|
|
||||||
|
|
||||||
|
if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame)
|
||||||
|
{
|
||||||
|
pose = predictedPoses(phase).eye[(int)side];
|
||||||
|
osg::Vec3 position = pose.position * Environment::get().unitsPerMeter();
|
||||||
|
osg::Quat orientation = pose.orientation;
|
||||||
|
osg::Vec3d forward = orientation * osg::Vec3d(0, 1, 0);
|
||||||
|
osg::Vec3d up = orientation * osg::Vec3d(0, 0, 1);
|
||||||
|
osg::Matrix viewMatrix;
|
||||||
|
viewMatrix.makeLookAt(position, position + forward, up);
|
||||||
|
|
||||||
|
return viewMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
osg::Vec3 position = pose.position * Environment::get().unitsPerMeter();
|
osg::Vec3 position = pose.position * Environment::get().unitsPerMeter();
|
||||||
osg::Quat orientation = pose.orientation;
|
osg::Quat orientation = pose.orientation;
|
||||||
osg::Vec3d forward = orientation * osg::Vec3d(0, 1, 0);
|
|
||||||
osg::Vec3d up = orientation * osg::Vec3d(0, 0, 1);
|
float y = position.y();
|
||||||
|
float z = position.z();
|
||||||
|
position.y() = z;
|
||||||
|
position.z() = -y;
|
||||||
|
|
||||||
|
y = orientation.y();
|
||||||
|
z = orientation.z();
|
||||||
|
orientation.y() = z;
|
||||||
|
orientation.z() = -y;
|
||||||
|
|
||||||
osg::Matrix viewMatrix;
|
osg::Matrix viewMatrix;
|
||||||
viewMatrix.makeLookAt(position, position + forward, up);
|
viewMatrix.setTrans(-position);
|
||||||
|
viewMatrix.postMultRotate(orientation.conj());
|
||||||
|
|
||||||
return viewMatrix;
|
return viewMatrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Vec3 position = pose.position * Environment::get().unitsPerMeter();
|
bool VRSession::isRunning() const {
|
||||||
osg::Quat orientation = pose.orientation;
|
auto* xr = Environment::get().getManager();
|
||||||
|
return xr->xrSessionRunning();
|
||||||
float y = position.y();
|
|
||||||
float z = position.z();
|
|
||||||
position.y() = z;
|
|
||||||
position.z() = -y;
|
|
||||||
|
|
||||||
y = orientation.y();
|
|
||||||
z = orientation.z();
|
|
||||||
orientation.y() = z;
|
|
||||||
orientation.z() = -y;
|
|
||||||
|
|
||||||
osg::Matrix viewMatrix;
|
|
||||||
viewMatrix.setTrans(-position);
|
|
||||||
viewMatrix.postMultRotate(orientation.conj());
|
|
||||||
|
|
||||||
return viewMatrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VRSession::isRunning() const {
|
|
||||||
auto* xr = Environment::get().getManager();
|
|
||||||
return xr->xrSessionRunning();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VRSession::swapBuffers(osg::GraphicsContext* gc, VRViewer& viewer)
|
|
||||||
{
|
|
||||||
auto* xr = Environment::get().getManager();
|
|
||||||
|
|
||||||
beginPhase(FramePhase::Swap);
|
|
||||||
|
|
||||||
auto* frameMeta = getFrame(FramePhase::Swap).get();
|
|
||||||
|
|
||||||
if (frameMeta->mShouldRender && isRunning())
|
|
||||||
{
|
|
||||||
auto leftView = viewer.mViews["LeftEye"];
|
|
||||||
auto rightView = viewer.mViews["RightEye"];
|
|
||||||
|
|
||||||
viewer.blitEyesToMirrorTexture(gc);
|
|
||||||
gc->swapBuffersImplementation();
|
|
||||||
leftView->swapBuffers(gc);
|
|
||||||
rightView->swapBuffers(gc);
|
|
||||||
|
|
||||||
std::array<CompositionLayerProjectionView, 2> layerStack{};
|
|
||||||
layerStack[(int)Side::LEFT_SIDE].swapchain = &leftView->swapchain();
|
|
||||||
layerStack[(int)Side::RIGHT_SIDE].swapchain = &rightView->swapchain();
|
|
||||||
layerStack[(int)Side::LEFT_SIDE].pose = frameMeta->mPredictedPoses.eye[(int)Side::LEFT_SIDE] / mPlayerScale;
|
|
||||||
layerStack[(int)Side::RIGHT_SIDE].pose = frameMeta->mPredictedPoses.eye[(int)Side::RIGHT_SIDE] / mPlayerScale;
|
|
||||||
layerStack[(int)Side::LEFT_SIDE].fov = frameMeta->mPredictedPoses.view[(int)Side::LEFT_SIDE].fov;
|
|
||||||
layerStack[(int)Side::RIGHT_SIDE].fov = frameMeta->mPredictedPoses.view[(int)Side::RIGHT_SIDE].fov;
|
|
||||||
|
|
||||||
Log(Debug::Debug) << frameMeta->mFrameNo << ": EndFrame " <<std::this_thread::get_id();
|
|
||||||
xr->endFrame(frameMeta->mPredictedDisplayTime, 1, layerStack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VRSession::swapBuffers(osg::GraphicsContext* gc, VRViewer& viewer)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
auto* xr = Environment::get().getManager();
|
||||||
|
|
||||||
// Some of these values are useless until the prediction time bug is resolved by oculus.
|
beginPhase(FramePhase::Swap);
|
||||||
auto now = std::chrono::steady_clock::now();
|
|
||||||
mLastFrameInterval = std::chrono::duration_cast<std::chrono::nanoseconds>(now - mLastRenderedFrameTimestamp);
|
|
||||||
mLastRenderedFrameTimestamp = now;
|
|
||||||
mLastRenderedFrame = getFrame(FramePhase::Swap)->mFrameNo;
|
|
||||||
|
|
||||||
// Using this to track framerate over the course of gameplay, rather than just seeing the instantaneous
|
auto* frameMeta = getFrame(FramePhase::Swap).get();
|
||||||
auto seconds = std::chrono::duration_cast<std::chrono::duration<double>>(now - mStart).count();
|
|
||||||
static int sBaseFrames = 0;
|
if (frameMeta->mShouldRender && isRunning())
|
||||||
if (seconds > 10.f)
|
|
||||||
{
|
{
|
||||||
Log(Debug::Verbose) << "Fps: " << (static_cast<double>(mLastRenderedFrame - sBaseFrames) / seconds);
|
auto leftView = viewer.getView("LeftEye");
|
||||||
mStart = now;
|
auto rightView = viewer.getView("RightEye");
|
||||||
sBaseFrames = mLastRenderedFrame;
|
|
||||||
|
viewer.blitEyesToMirrorTexture(gc);
|
||||||
|
gc->swapBuffersImplementation();
|
||||||
|
leftView->swapBuffers(gc);
|
||||||
|
rightView->swapBuffers(gc);
|
||||||
|
|
||||||
|
std::array<CompositionLayerProjectionView, 2> layerStack{};
|
||||||
|
layerStack[(int)Side::LEFT_SIDE].swapchain = &leftView->swapchain();
|
||||||
|
layerStack[(int)Side::RIGHT_SIDE].swapchain = &rightView->swapchain();
|
||||||
|
layerStack[(int)Side::LEFT_SIDE].pose = frameMeta->mPredictedPoses.eye[(int)Side::LEFT_SIDE] / mPlayerScale;
|
||||||
|
layerStack[(int)Side::RIGHT_SIDE].pose = frameMeta->mPredictedPoses.eye[(int)Side::RIGHT_SIDE] / mPlayerScale;
|
||||||
|
layerStack[(int)Side::LEFT_SIDE].fov = frameMeta->mPredictedPoses.view[(int)Side::LEFT_SIDE].fov;
|
||||||
|
layerStack[(int)Side::RIGHT_SIDE].fov = frameMeta->mPredictedPoses.view[(int)Side::RIGHT_SIDE].fov;
|
||||||
|
|
||||||
|
Log(Debug::Debug) << frameMeta->mFrameNo << ": EndFrame " << std::this_thread::get_id();
|
||||||
|
xr->endFrame(frameMeta->mPredictedDisplayTime, 1, layerStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFrame(FramePhase::Swap) = nullptr;
|
|
||||||
mFramesInFlight--;
|
|
||||||
}
|
|
||||||
mCondition.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VRSession::beginPhase(FramePhase phase)
|
|
||||||
{
|
|
||||||
Log(Debug::Debug) << "beginPhase(" << ((int)phase) << ") " << std::this_thread::get_id();
|
|
||||||
|
|
||||||
if (getFrame(phase))
|
|
||||||
{
|
|
||||||
Log(Debug::Warning) << "advanceFramePhase called with a frame alreay in the target phase";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (phase == FramePhase::Update)
|
|
||||||
{
|
|
||||||
prepareFrame();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
|
||||||
FramePhase previousPhase = static_cast<FramePhase>((int)phase - 1);
|
|
||||||
if (!getFrame(previousPhase))
|
|
||||||
throw std::logic_error("beginPhase called without a frame");
|
|
||||||
getFrame(phase) = std::move(getFrame(previousPhase));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: Invokation should depend on earliest render rather than necessarily phase.
|
|
||||||
// Specifically. Without shadows this is fine because nothing is being rendered
|
|
||||||
// during cull or earlier.
|
|
||||||
// Thought: Add an Shadowmapping phase and invoke it from the shadow code
|
|
||||||
// But with shadows rendering occurs during cull and we must do frame sync before those calls.
|
|
||||||
// If you want to pay the FPS toll and play with shadows, change FramePhase::Draw to FramePhase::Cull or enjoy your eyes getting torn apart by jitters.
|
|
||||||
if (phase == FramePhase::Cull && getFrame(phase)->mShouldRender)
|
|
||||||
doFrameSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VRSession::doFrameSync()
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
|
||||||
while (mLastRenderedFrame != mFrames - 1)
|
|
||||||
{
|
{
|
||||||
mCondition.wait(lock);
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
|
||||||
|
// Some of these values are useless until the prediction time bug is resolved by oculus.
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
mLastFrameInterval = std::chrono::duration_cast<std::chrono::nanoseconds>(now - mLastRenderedFrameTimestamp);
|
||||||
|
mLastRenderedFrameTimestamp = now;
|
||||||
|
mLastRenderedFrame = getFrame(FramePhase::Swap)->mFrameNo;
|
||||||
|
|
||||||
|
// Using this to track framerate over the course of gameplay, rather than just seeing the instantaneous
|
||||||
|
auto seconds = std::chrono::duration_cast<std::chrono::duration<double>>(now - mStart).count();
|
||||||
|
static int sBaseFrames = 0;
|
||||||
|
if (seconds > 10.f)
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << "Fps: " << (static_cast<double>(mLastRenderedFrame - sBaseFrames) / seconds);
|
||||||
|
mStart = now;
|
||||||
|
sBaseFrames = mLastRenderedFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFrame(FramePhase::Swap) = nullptr;
|
||||||
|
mFramesInFlight--;
|
||||||
}
|
}
|
||||||
|
mCondition.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* xr = Environment::get().getManager();
|
void VRSession::beginPhase(FramePhase phase)
|
||||||
Log(Debug::Debug) << mFrames << ": WaitFrame " << std::this_thread::get_id();
|
|
||||||
xr->waitFrame();
|
|
||||||
Log(Debug::Debug) << mFrames << ": BeginFrame " << std::this_thread::get_id();
|
|
||||||
xr->beginFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<VRSession::VRFrameMeta>& VRSession::getFrame(FramePhase phase)
|
|
||||||
{
|
|
||||||
if ((unsigned int)phase >= mFrame.size())
|
|
||||||
throw std::logic_error("Invalid frame phase");
|
|
||||||
return mFrame[(int)phase];
|
|
||||||
}
|
|
||||||
|
|
||||||
void VRSession::prepareFrame()
|
|
||||||
{
|
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
|
||||||
mFrames++;
|
|
||||||
assert(!mPredrawFrame);
|
|
||||||
|
|
||||||
auto* xr = Environment::get().getManager();
|
|
||||||
xr->handleEvents();
|
|
||||||
|
|
||||||
//auto frameState = xr->impl().frameState();
|
|
||||||
// auto predictedDisplayTime = frameState.predictedDisplayTime;
|
|
||||||
// if (predictedDisplayTime == 0)
|
|
||||||
// {
|
|
||||||
// // First time, need to invent a frame time since openxr won't help us without a call to waitframe.
|
|
||||||
// predictedDisplayTime = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// // Predict display time based on real framerate
|
|
||||||
// float intervalsf = static_cast<double>(mLastFrameInterval.count()) / static_cast<double>(mLastPredictedDisplayPeriod);
|
|
||||||
// int intervals = std::max((int)std::roundf(intervalsf), 1);
|
|
||||||
// predictedDisplayTime = mLastPredictedDisplayTime + intervals * (mFrames - mLastRenderedFrame) * mLastPredictedDisplayPeriod;
|
|
||||||
// }
|
|
||||||
|
|
||||||
//////////////////////// OCULUS BUG
|
|
||||||
//////////////////// Oculus will suddenly start monotonically increasing their predicted display time by precisely 1 second
|
|
||||||
//////////////////// regardless of real time passed, causing predictions to go crazy due to the time difference.
|
|
||||||
//////////////////// Therefore, for the time being, i ignore oculus' predicted display time altogether.
|
|
||||||
long long predictedDisplayTime = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
|
||||||
if (mFrames > 1)
|
|
||||||
{
|
{
|
||||||
auto framePeriod = xr->getLastPredictedDisplayPeriod();
|
Log(Debug::Debug) << "beginPhase(" << ((int)phase) << ") " << std::this_thread::get_id();
|
||||||
float intervalsf = static_cast<double>(mLastFrameInterval.count()) / static_cast<double>(framePeriod);
|
|
||||||
int intervals = std::max((int)std::roundf(intervalsf), 1);
|
if (getFrame(phase))
|
||||||
predictedDisplayTime = predictedDisplayTime + intervals * (mFrames - mLastRenderedFrame) * framePeriod;
|
{
|
||||||
|
Log(Debug::Warning) << "advanceFramePhase called with a frame alreay in the target phase";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (phase == FramePhase::Update)
|
||||||
|
{
|
||||||
|
prepareFrame();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
FramePhase previousPhase = static_cast<FramePhase>((int)phase - 1);
|
||||||
|
if (!getFrame(previousPhase))
|
||||||
|
throw std::logic_error("beginPhase called without a frame");
|
||||||
|
getFrame(phase) = std::move(getFrame(previousPhase));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Invokation should depend on earliest render rather than necessarily phase.
|
||||||
|
// Specifically. Without shadows this is fine because nothing is being rendered
|
||||||
|
// during cull or earlier.
|
||||||
|
// Thought: Add an Shadowmapping phase and invoke it from the shadow code
|
||||||
|
// But with shadows rendering occurs during cull and we must do frame sync before those calls.
|
||||||
|
// If you want to pay the FPS toll and play with shadows, change FramePhase::Draw to FramePhase::Cull or enjoy your eyes getting torn apart by jitters.
|
||||||
|
if (phase == FramePhase::Cull && getFrame(phase)->mShouldRender)
|
||||||
|
doFrameSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VRSession::doFrameSync()
|
||||||
|
|
||||||
PoseSet predictedPoses{};
|
|
||||||
|
|
||||||
xr->enablePredictions();
|
|
||||||
predictedPoses.head = xr->getPredictedHeadPose(predictedDisplayTime, ReferenceSpace::STAGE) * mPlayerScale;
|
|
||||||
auto hmdViews = xr->getPredictedViews(predictedDisplayTime, ReferenceSpace::VIEW);
|
|
||||||
predictedPoses.view[(int)Side::LEFT_SIDE].pose = hmdViews[(int)Side::LEFT_SIDE].pose * mPlayerScale;
|
|
||||||
predictedPoses.view[(int)Side::RIGHT_SIDE].pose = hmdViews[(int)Side::RIGHT_SIDE].pose * mPlayerScale;
|
|
||||||
predictedPoses.view[(int)Side::LEFT_SIDE].fov = hmdViews[(int)Side::LEFT_SIDE].fov;
|
|
||||||
predictedPoses.view[(int)Side::RIGHT_SIDE].fov = hmdViews[(int)Side::RIGHT_SIDE].fov;
|
|
||||||
auto stageViews = xr->getPredictedViews(predictedDisplayTime, ReferenceSpace::STAGE);
|
|
||||||
predictedPoses.eye[(int)Side::LEFT_SIDE] = stageViews[(int)Side::LEFT_SIDE].pose * mPlayerScale;
|
|
||||||
predictedPoses.eye[(int)Side::RIGHT_SIDE] = stageViews[(int)Side::RIGHT_SIDE].pose * mPlayerScale;
|
|
||||||
|
|
||||||
auto* input = Environment::get().getInputManager();
|
|
||||||
if (input)
|
|
||||||
{
|
{
|
||||||
predictedPoses.hands[(int)Side::LEFT_SIDE] = input->getLimbPose(predictedDisplayTime, TrackedLimb::LEFT_HAND) * mPlayerScale;
|
{
|
||||||
predictedPoses.hands[(int)Side::RIGHT_SIDE] = input->getLimbPose(predictedDisplayTime, TrackedLimb::RIGHT_HAND) * mPlayerScale;
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
while (mLastRenderedFrame != mFrames - 1)
|
||||||
|
{
|
||||||
|
mCondition.wait(lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
Log(Debug::Debug) << mFrames << ": WaitFrame " << std::this_thread::get_id();
|
||||||
|
xr->waitFrame();
|
||||||
|
Log(Debug::Debug) << mFrames << ": BeginFrame " << std::this_thread::get_id();
|
||||||
|
xr->beginFrame();
|
||||||
}
|
}
|
||||||
xr->disablePredictions();
|
|
||||||
|
|
||||||
auto& frame = getFrame(FramePhase::Update);
|
std::unique_ptr<VRSession::VRFrameMeta>& VRSession::getFrame(FramePhase phase)
|
||||||
frame.reset(new VRFrameMeta);
|
|
||||||
frame->mPredictedDisplayTime = predictedDisplayTime;
|
|
||||||
frame->mFrameNo = mFrames;
|
|
||||||
frame->mPredictedPoses = predictedPoses;
|
|
||||||
frame->mShouldRender = isRunning();
|
|
||||||
mFramesInFlight++;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PoseSet& VRSession::predictedPoses(FramePhase phase)
|
|
||||||
{
|
|
||||||
auto& frame = getFrame(phase);
|
|
||||||
|
|
||||||
if (phase == FramePhase::Update && !frame)
|
|
||||||
beginPhase(FramePhase::Update);
|
|
||||||
|
|
||||||
if (!frame)
|
|
||||||
throw std::logic_error("Attempted to get poses from a phase with no current pose");
|
|
||||||
return frame->mPredictedPoses;
|
|
||||||
}
|
|
||||||
|
|
||||||
// OSG doesn't provide API to extract euler angles from a quat, but i need it.
|
|
||||||
// Credits goes to Dennis Bunfield, i just copied his formula https://narkive.com/v0re6547.4
|
|
||||||
void getEulerAngles(const osg::Quat& quat, float& yaw, float& pitch, float& roll)
|
|
||||||
{
|
|
||||||
// Now do the computation
|
|
||||||
osg::Matrixd m2(osg::Matrixd::rotate(quat));
|
|
||||||
double* mat = (double*)m2.ptr();
|
|
||||||
double angle_x = 0.0;
|
|
||||||
double angle_y = 0.0;
|
|
||||||
double angle_z = 0.0;
|
|
||||||
double D, C, tr_x, tr_y;
|
|
||||||
angle_y = D = asin(mat[2]); /* Calculate Y-axis angle */
|
|
||||||
C = cos(angle_y);
|
|
||||||
if (fabs(C) > 0.005) /* Test for Gimball lock? */
|
|
||||||
{
|
{
|
||||||
tr_x = mat[10] / C; /* No, so get X-axis angle */
|
if ((unsigned int)phase >= mFrame.size())
|
||||||
tr_y = -mat[6] / C;
|
throw std::logic_error("Invalid frame phase");
|
||||||
angle_x = atan2(tr_y, tr_x);
|
return mFrame[(int)phase];
|
||||||
tr_x = mat[0] / C; /* Get Z-axis angle */
|
|
||||||
tr_y = -mat[1] / C;
|
|
||||||
angle_z = atan2(tr_y, tr_x);
|
|
||||||
}
|
}
|
||||||
else /* Gimball lock has occurred */
|
|
||||||
|
void VRSession::prepareFrame()
|
||||||
{
|
{
|
||||||
angle_x = 0; /* Set X-axis angle to zero
|
|
||||||
*/
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
tr_x = mat[5]; /* And calculate Z-axis angle
|
mFrames++;
|
||||||
*/
|
assert(!mPredrawFrame);
|
||||||
tr_y = mat[4];
|
|
||||||
angle_z = atan2(tr_y, tr_x);
|
auto* xr = Environment::get().getManager();
|
||||||
|
xr->handleEvents();
|
||||||
|
|
||||||
|
//auto frameState = xr->impl().frameState();
|
||||||
|
// auto predictedDisplayTime = frameState.predictedDisplayTime;
|
||||||
|
// if (predictedDisplayTime == 0)
|
||||||
|
// {
|
||||||
|
// // First time, need to invent a frame time since openxr won't help us without a call to waitframe.
|
||||||
|
// predictedDisplayTime = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// // Predict display time based on real framerate
|
||||||
|
// float intervalsf = static_cast<double>(mLastFrameInterval.count()) / static_cast<double>(mLastPredictedDisplayPeriod);
|
||||||
|
// int intervals = std::max((int)std::roundf(intervalsf), 1);
|
||||||
|
// predictedDisplayTime = mLastPredictedDisplayTime + intervals * (mFrames - mLastRenderedFrame) * mLastPredictedDisplayPeriod;
|
||||||
|
// }
|
||||||
|
|
||||||
|
//////////////////////// OCULUS BUG
|
||||||
|
//////////////////// Oculus will suddenly start monotonically increasing their predicted display time by precisely 1 second
|
||||||
|
//////////////////// regardless of real time passed, causing predictions to go crazy due to the time difference.
|
||||||
|
//////////////////// Therefore, for the time being, i ignore oculus' predicted display time altogether.
|
||||||
|
long long predictedDisplayTime = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||||
|
if (mFrames > 1)
|
||||||
|
{
|
||||||
|
auto framePeriod = xr->getLastPredictedDisplayPeriod();
|
||||||
|
float intervalsf = static_cast<double>(mLastFrameInterval.count()) / static_cast<double>(framePeriod);
|
||||||
|
int intervals = std::max((int)std::roundf(intervalsf), 1);
|
||||||
|
predictedDisplayTime = predictedDisplayTime + intervals * (mFrames - mLastRenderedFrame) * framePeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PoseSet predictedPoses{};
|
||||||
|
|
||||||
|
xr->enablePredictions();
|
||||||
|
predictedPoses.head = xr->getPredictedHeadPose(predictedDisplayTime, ReferenceSpace::STAGE) * mPlayerScale;
|
||||||
|
auto hmdViews = xr->getPredictedViews(predictedDisplayTime, ReferenceSpace::VIEW);
|
||||||
|
predictedPoses.view[(int)Side::LEFT_SIDE].pose = hmdViews[(int)Side::LEFT_SIDE].pose * mPlayerScale;
|
||||||
|
predictedPoses.view[(int)Side::RIGHT_SIDE].pose = hmdViews[(int)Side::RIGHT_SIDE].pose * mPlayerScale;
|
||||||
|
predictedPoses.view[(int)Side::LEFT_SIDE].fov = hmdViews[(int)Side::LEFT_SIDE].fov;
|
||||||
|
predictedPoses.view[(int)Side::RIGHT_SIDE].fov = hmdViews[(int)Side::RIGHT_SIDE].fov;
|
||||||
|
auto stageViews = xr->getPredictedViews(predictedDisplayTime, ReferenceSpace::STAGE);
|
||||||
|
predictedPoses.eye[(int)Side::LEFT_SIDE] = stageViews[(int)Side::LEFT_SIDE].pose * mPlayerScale;
|
||||||
|
predictedPoses.eye[(int)Side::RIGHT_SIDE] = stageViews[(int)Side::RIGHT_SIDE].pose * mPlayerScale;
|
||||||
|
|
||||||
|
auto* input = Environment::get().getInputManager();
|
||||||
|
if (input)
|
||||||
|
{
|
||||||
|
predictedPoses.hands[(int)Side::LEFT_SIDE] = input->getLimbPose(predictedDisplayTime, TrackedLimb::LEFT_HAND) * mPlayerScale;
|
||||||
|
predictedPoses.hands[(int)Side::RIGHT_SIDE] = input->getLimbPose(predictedDisplayTime, TrackedLimb::RIGHT_HAND) * mPlayerScale;
|
||||||
|
}
|
||||||
|
xr->disablePredictions();
|
||||||
|
|
||||||
|
auto& frame = getFrame(FramePhase::Update);
|
||||||
|
frame.reset(new VRFrameMeta);
|
||||||
|
frame->mPredictedDisplayTime = predictedDisplayTime;
|
||||||
|
frame->mFrameNo = mFrames;
|
||||||
|
frame->mPredictedPoses = predictedPoses;
|
||||||
|
frame->mShouldRender = isRunning();
|
||||||
|
mFramesInFlight++;
|
||||||
}
|
}
|
||||||
|
|
||||||
yaw = angle_z;
|
const PoseSet& VRSession::predictedPoses(FramePhase phase)
|
||||||
pitch = angle_x;
|
{
|
||||||
roll = angle_y;
|
auto& frame = getFrame(phase);
|
||||||
}
|
|
||||||
|
|
||||||
void VRSession::movementAngles(float& yaw, float& pitch)
|
if (phase == FramePhase::Update && !frame)
|
||||||
{
|
beginPhase(FramePhase::Update);
|
||||||
assert(mPredrawFrame);
|
|
||||||
if (!getFrame(FramePhase::Update))
|
|
||||||
beginPhase(FramePhase::Update);
|
|
||||||
auto frameMeta = getFrame(FramePhase::Update).get();
|
|
||||||
|
|
||||||
float headYaw = 0.f;
|
|
||||||
float headPitch = 0.f;
|
|
||||||
float headsWillRoll = 0.f;
|
|
||||||
|
|
||||||
float handYaw = 0.f;
|
if (!frame)
|
||||||
float handPitch = 0.f;
|
throw std::logic_error("Attempted to get poses from a phase with no current pose");
|
||||||
float handRoll = 0.f;
|
return frame->mPredictedPoses;
|
||||||
|
}
|
||||||
|
|
||||||
getEulerAngles(frameMeta->mPredictedPoses.head.orientation, headYaw, headPitch, headsWillRoll);
|
// OSG doesn't provide API to extract euler angles from a quat, but i need it.
|
||||||
getEulerAngles(frameMeta->mPredictedPoses.hands[(int)Side::LEFT_SIDE].orientation, handYaw, handPitch, handRoll);
|
// Credits goes to Dennis Bunfield, i just copied his formula https://narkive.com/v0re6547.4
|
||||||
|
void getEulerAngles(const osg::Quat& quat, float& yaw, float& pitch, float& roll)
|
||||||
|
{
|
||||||
|
// Now do the computation
|
||||||
|
osg::Matrixd m2(osg::Matrixd::rotate(quat));
|
||||||
|
double* mat = (double*)m2.ptr();
|
||||||
|
double angle_x = 0.0;
|
||||||
|
double angle_y = 0.0;
|
||||||
|
double angle_z = 0.0;
|
||||||
|
double D, C, tr_x, tr_y;
|
||||||
|
angle_y = D = asin(mat[2]); /* Calculate Y-axis angle */
|
||||||
|
C = cos(angle_y);
|
||||||
|
if (fabs(C) > 0.005) /* Test for Gimball lock? */
|
||||||
|
{
|
||||||
|
tr_x = mat[10] / C; /* No, so get X-axis angle */
|
||||||
|
tr_y = -mat[6] / C;
|
||||||
|
angle_x = atan2(tr_y, tr_x);
|
||||||
|
tr_x = mat[0] / C; /* Get Z-axis angle */
|
||||||
|
tr_y = -mat[1] / C;
|
||||||
|
angle_z = atan2(tr_y, tr_x);
|
||||||
|
}
|
||||||
|
else /* Gimball lock has occurred */
|
||||||
|
{
|
||||||
|
angle_x = 0; /* Set X-axis angle to zero
|
||||||
|
*/
|
||||||
|
tr_x = mat[5]; /* And calculate Z-axis angle
|
||||||
|
*/
|
||||||
|
tr_y = mat[4];
|
||||||
|
angle_z = atan2(tr_y, tr_x);
|
||||||
|
}
|
||||||
|
|
||||||
//Log(Debug::Verbose) << "lhandViewYaw=" << yaw << ", headYaw=" << headYaw << ", handYaw=" << handYaw << ", diff=" << (handYaw - headYaw);
|
yaw = angle_z;
|
||||||
//Log(Debug::Verbose) << "lhandViewPitch=" << pitch << ", headPitch=" << headPitch << ", handPitch=" << handPitch << ", diff=" << (handPitch - headPitch);
|
pitch = angle_x;
|
||||||
yaw = handYaw - headYaw;
|
roll = angle_y;
|
||||||
pitch = handPitch - headPitch;
|
}
|
||||||
}
|
|
||||||
|
void VRSession::movementAngles(float& yaw, float& pitch)
|
||||||
|
{
|
||||||
|
assert(mPredrawFrame);
|
||||||
|
if (!getFrame(FramePhase::Update))
|
||||||
|
beginPhase(FramePhase::Update);
|
||||||
|
auto frameMeta = getFrame(FramePhase::Update).get();
|
||||||
|
|
||||||
|
float headYaw = 0.f;
|
||||||
|
float headPitch = 0.f;
|
||||||
|
float headsWillRoll = 0.f;
|
||||||
|
|
||||||
|
float handYaw = 0.f;
|
||||||
|
float handPitch = 0.f;
|
||||||
|
float handRoll = 0.f;
|
||||||
|
|
||||||
|
getEulerAngles(frameMeta->mPredictedPoses.head.orientation, headYaw, headPitch, headsWillRoll);
|
||||||
|
getEulerAngles(frameMeta->mPredictedPoses.hands[(int)Side::LEFT_SIDE].orientation, handYaw, handPitch, handRoll);
|
||||||
|
|
||||||
|
//Log(Debug::Verbose) << "lhandViewYaw=" << yaw << ", headYaw=" << headYaw << ", handYaw=" << handYaw << ", diff=" << (handYaw - headYaw);
|
||||||
|
//Log(Debug::Verbose) << "lhandViewPitch=" << pitch << ", headPitch=" << headPitch << ", handPitch=" << handPitch << ", diff=" << (handPitch - headPitch);
|
||||||
|
yaw = handYaw - headYaw;
|
||||||
|
pitch = handPitch - headPitch;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,84 +15,85 @@
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
|
||||||
extern void getEulerAngles(const osg::Quat& quat, float& yaw, float& pitch, float& roll);
|
extern void getEulerAngles(const osg::Quat& quat, float& yaw, float& pitch, float& roll);
|
||||||
|
|
||||||
//! Manages VR logic, such as managing frames, predicting their poses, and handling frame synchronization.
|
/// \brief Manages VR logic, such as managing frames, predicting their poses, and handling frame synchronization with the VR runtime.
|
||||||
//! Should not be confused with the openxr session object.
|
/// Should not be confused with the openxr session object.
|
||||||
class VRSession
|
class VRSession
|
||||||
{
|
|
||||||
public:
|
|
||||||
using seconds = std::chrono::duration<double>;
|
|
||||||
using nanoseconds = std::chrono::nanoseconds;
|
|
||||||
using clock = std::chrono::steady_clock;
|
|
||||||
using time_point = clock::time_point;
|
|
||||||
|
|
||||||
//! Describes different phases of updating/rendering each frame.
|
|
||||||
//! While two phases suffice for disambiguating current and next frame,
|
|
||||||
//! greater granularity allows better control of synchronization
|
|
||||||
//! with the VR runtime.
|
|
||||||
enum class FramePhase
|
|
||||||
{
|
{
|
||||||
Update = 0, //!< The frame currently in update traversals
|
public:
|
||||||
Cull, //!< The frame currently in cull
|
using seconds = std::chrono::duration<double>;
|
||||||
Draw, //!< The frame currently in draw
|
using nanoseconds = std::chrono::nanoseconds;
|
||||||
Swap, //!< The frame being swapped
|
using clock = std::chrono::steady_clock;
|
||||||
NumPhases
|
using time_point = clock::time_point;
|
||||||
|
|
||||||
|
//! Describes different phases of updating/rendering each frame.
|
||||||
|
//! While two phases suffice for disambiguating current and next frame,
|
||||||
|
//! greater granularity allows better control of synchronization
|
||||||
|
//! with the VR runtime.
|
||||||
|
enum class FramePhase
|
||||||
|
{
|
||||||
|
Update = 0, //!< The frame currently in update traversals
|
||||||
|
Cull, //!< The frame currently in cull
|
||||||
|
Draw, //!< The frame currently in draw
|
||||||
|
Swap, //!< The frame being swapped
|
||||||
|
NumPhases
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VRFrameMeta
|
||||||
|
{
|
||||||
|
long long mFrameNo{ 0 };
|
||||||
|
long long mPredictedDisplayTime{ 0 };
|
||||||
|
PoseSet mPredictedPoses{};
|
||||||
|
bool mShouldRender{ false };
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
VRSession();
|
||||||
|
~VRSession();
|
||||||
|
|
||||||
|
void swapBuffers(osg::GraphicsContext* gc, VRViewer& viewer);
|
||||||
|
|
||||||
|
const PoseSet& predictedPoses(FramePhase phase);
|
||||||
|
|
||||||
|
//! Starts a new frame
|
||||||
|
void prepareFrame();
|
||||||
|
|
||||||
|
//! Synchronize with openxr
|
||||||
|
void doFrameSync();
|
||||||
|
|
||||||
|
//! Angles to be used for overriding movement direction
|
||||||
|
void movementAngles(float& yaw, float& pitch);
|
||||||
|
|
||||||
|
void beginPhase(FramePhase phase);
|
||||||
|
std::unique_ptr<VRFrameMeta>& getFrame(FramePhase phase);
|
||||||
|
|
||||||
|
bool isRunning() const;
|
||||||
|
|
||||||
|
float playerScale() const { return mPlayerScale; }
|
||||||
|
float setPlayerScale(float scale) { return mPlayerScale = scale; }
|
||||||
|
|
||||||
|
osg::Matrix viewMatrix(FramePhase phase, Side side);
|
||||||
|
osg::Matrix projectionMatrix(FramePhase phase, Side side);
|
||||||
|
|
||||||
|
int mFramesInFlight{ 0 };
|
||||||
|
std::array<std::unique_ptr<VRFrameMeta>, (int)FramePhase::NumPhases> mFrame{ nullptr };
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex mMutex{};
|
||||||
|
std::condition_variable mCondition{};
|
||||||
|
|
||||||
|
long long mFrames{ 0 };
|
||||||
|
long long mLastRenderedFrame{ 0 };
|
||||||
|
long long mLastPredictedDisplayTime{ 0 };
|
||||||
|
long long mLastPredictedDisplayPeriod{ 0 };
|
||||||
|
std::chrono::steady_clock::time_point mStart{ std::chrono::steady_clock::now() };
|
||||||
|
std::chrono::nanoseconds mLastFrameInterval{};
|
||||||
|
std::chrono::steady_clock::time_point mLastRenderedFrameTimestamp{ std::chrono::steady_clock::now() };
|
||||||
|
|
||||||
|
float mPlayerScale{ 1.f };
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VRFrameMeta
|
|
||||||
{
|
|
||||||
long long mFrameNo{ 0 };
|
|
||||||
long long mPredictedDisplayTime{ 0 };
|
|
||||||
PoseSet mPredictedPoses{};
|
|
||||||
bool mShouldRender{ false };
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
VRSession();
|
|
||||||
~VRSession();
|
|
||||||
|
|
||||||
void swapBuffers(osg::GraphicsContext* gc, VRViewer& viewer);
|
|
||||||
|
|
||||||
const PoseSet& predictedPoses(FramePhase phase);
|
|
||||||
|
|
||||||
//! Starts a new frame
|
|
||||||
void prepareFrame();
|
|
||||||
|
|
||||||
//! Synchronize with openxr
|
|
||||||
void doFrameSync();
|
|
||||||
|
|
||||||
//! Angles to be used for overriding movement direction
|
|
||||||
void movementAngles(float& yaw, float& pitch);
|
|
||||||
|
|
||||||
void beginPhase(FramePhase phase);
|
|
||||||
std::unique_ptr<VRFrameMeta>& getFrame(FramePhase phase);
|
|
||||||
|
|
||||||
bool isRunning() const;
|
|
||||||
|
|
||||||
float playerScale() const { return mPlayerScale; }
|
|
||||||
float setPlayerScale(float scale) { return mPlayerScale = scale; }
|
|
||||||
|
|
||||||
osg::Matrix viewMatrix(FramePhase phase, Side side);
|
|
||||||
osg::Matrix projectionMatrix(FramePhase phase, Side side);
|
|
||||||
|
|
||||||
int mFramesInFlight{ 0 };
|
|
||||||
std::array<std::unique_ptr<VRFrameMeta>, (int)FramePhase::NumPhases> mFrame{ nullptr };
|
|
||||||
|
|
||||||
std::mutex mMutex{};
|
|
||||||
std::condition_variable mCondition{};
|
|
||||||
|
|
||||||
long long mFrames{ 0 };
|
|
||||||
long long mLastRenderedFrame{ 0 };
|
|
||||||
long long mLastPredictedDisplayTime{ 0 };
|
|
||||||
long long mLastPredictedDisplayPeriod{ 0 };
|
|
||||||
std::chrono::steady_clock::time_point mStart{ std::chrono::steady_clock::now() };
|
|
||||||
std::chrono::nanoseconds mLastFrameInterval{};
|
|
||||||
std::chrono::steady_clock::time_point mLastRenderedFrameTimestamp{std::chrono::steady_clock::now()};
|
|
||||||
|
|
||||||
float mPlayerScale{ 1.f };
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
#include "vrviewer.hpp"
|
|
||||||
#include "vrtexture.hpp"
|
#include "vrtexture.hpp"
|
||||||
|
|
||||||
#include <osg/Texture2D>
|
#include <osg/Texture2D>
|
||||||
#include <osgViewer/Renderer>
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <osgDB/Registry>
|
|
||||||
#include <sstream>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
#ifndef GL_TEXTURE_MAX_LEVEL
|
#ifndef GL_TEXTURE_MAX_LEVEL
|
||||||
#define GL_TEXTURE_MAX_LEVEL 0x813D
|
#define GL_TEXTURE_MAX_LEVEL 0x813D
|
||||||
|
@ -18,9 +15,9 @@ namespace MWVR
|
||||||
: mState(state)
|
: mState(state)
|
||||||
, mWidth(width)
|
, mWidth(width)
|
||||||
, mHeight(height)
|
, mHeight(height)
|
||||||
, mSamples(msaaSamples)
|
|
||||||
, mColorBuffer(colorBuffer)
|
|
||||||
, mDepthBuffer(depthBuffer)
|
, mDepthBuffer(depthBuffer)
|
||||||
|
, mColorBuffer(colorBuffer)
|
||||||
|
, mSamples(msaaSamples)
|
||||||
{
|
{
|
||||||
auto* gl = osg::GLExtensions::Get(state->getContextID(), false);
|
auto* gl = osg::GLExtensions::Get(state->getContextID(), false);
|
||||||
|
|
||||||
|
@ -94,10 +91,10 @@ namespace MWVR
|
||||||
if (mFBO)
|
if (mFBO)
|
||||||
gl->glDeleteFramebuffers(1, &mFBO);
|
gl->glDeleteFramebuffers(1, &mFBO);
|
||||||
}
|
}
|
||||||
else if(mFBO)
|
else if (mFBO)
|
||||||
// Without access to opengl methods, i'll just let the FBOs leak.
|
// Without access to opengl methods, i'll just let the FBOs leak.
|
||||||
Log(Debug::Warning) << "destroy() called without a State. Leaking FBO";
|
Log(Debug::Warning) << "destroy() called without a State. Leaking FBO";
|
||||||
|
|
||||||
if (mDepthBuffer)
|
if (mDepthBuffer)
|
||||||
glDeleteTextures(1, &mDepthBuffer);
|
glDeleteTextures(1, &mDepthBuffer);
|
||||||
if (mColorBuffer)
|
if (mColorBuffer)
|
||||||
|
|
|
@ -1,202 +1,203 @@
|
||||||
#include "vrtypes.hpp"
|
#include "vrtypes.hpp"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
Pose Pose::operator+(const Pose& rhs)
|
Pose Pose::operator+(const Pose& rhs)
|
||||||
{
|
|
||||||
Pose pose = *this;
|
|
||||||
pose.position += this->orientation * rhs.position;
|
|
||||||
pose.orientation = rhs.orientation * this->orientation;
|
|
||||||
return pose;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Pose& Pose::operator+=(const Pose& rhs)
|
|
||||||
{
|
|
||||||
*this = *this + rhs;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Pose Pose::operator*(float scalar)
|
|
||||||
{
|
|
||||||
Pose pose = *this;
|
|
||||||
pose.position *= scalar;
|
|
||||||
return pose;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Pose& Pose::operator*=(float scalar)
|
|
||||||
{
|
|
||||||
*this = *this * scalar;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Pose Pose::operator/(float scalar)
|
|
||||||
{
|
|
||||||
Pose pose = *this;
|
|
||||||
pose.position /= scalar;
|
|
||||||
return pose;
|
|
||||||
}
|
|
||||||
const Pose& Pose::operator/=(float scalar)
|
|
||||||
{
|
|
||||||
*this = *this / scalar;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Pose::operator==(const Pose& rhs) const
|
|
||||||
{
|
|
||||||
return position == rhs.position && orientation == rhs.orientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FieldOfView::operator==(const FieldOfView& rhs) const
|
|
||||||
{
|
|
||||||
return angleDown == rhs.angleDown
|
|
||||||
&& angleUp == rhs.angleUp
|
|
||||||
&& angleLeft == rhs.angleLeft
|
|
||||||
&& angleRight == rhs.angleRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// near and far named with an underscore because of windows' headers galaxy brain defines.
|
|
||||||
osg::Matrix FieldOfView::perspectiveMatrix(float near_, float far_)
|
|
||||||
{
|
|
||||||
const float tanLeft = tanf(angleLeft);
|
|
||||||
const float tanRight = tanf(angleRight);
|
|
||||||
const float tanDown = tanf(angleDown);
|
|
||||||
const float tanUp = tanf(angleUp);
|
|
||||||
|
|
||||||
const float tanWidth = tanRight - tanLeft;
|
|
||||||
const float tanHeight = tanUp - tanDown;
|
|
||||||
|
|
||||||
const float offset = near_;
|
|
||||||
|
|
||||||
float matrix[16] = {};
|
|
||||||
|
|
||||||
matrix[0] = 2 / tanWidth;
|
|
||||||
matrix[4] = 0;
|
|
||||||
matrix[8] = (tanRight + tanLeft) / tanWidth;
|
|
||||||
matrix[12] = 0;
|
|
||||||
|
|
||||||
matrix[1] = 0;
|
|
||||||
matrix[5] = 2 / tanHeight;
|
|
||||||
matrix[9] = (tanUp + tanDown) / tanHeight;
|
|
||||||
matrix[13] = 0;
|
|
||||||
|
|
||||||
if (far_ <= near_) {
|
|
||||||
matrix[2] = 0;
|
|
||||||
matrix[6] = 0;
|
|
||||||
matrix[10] = -1;
|
|
||||||
matrix[14] = -(near_ + offset);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
matrix[2] = 0;
|
|
||||||
matrix[6] = 0;
|
|
||||||
matrix[10] = -(far_ + offset) / (far_ - near_);
|
|
||||||
matrix[14] = -(far_ * (near_ + offset)) / (far_ - near_);
|
|
||||||
}
|
|
||||||
|
|
||||||
matrix[3] = 0;
|
|
||||||
matrix[7] = 0;
|
|
||||||
matrix[11] = -1;
|
|
||||||
matrix[15] = 0;
|
|
||||||
|
|
||||||
return osg::Matrix(matrix);
|
|
||||||
}
|
|
||||||
bool PoseSet::operator==(const PoseSet& rhs) const
|
|
||||||
{
|
|
||||||
return eye[0] == rhs.eye[0]
|
|
||||||
&& eye[1] == rhs.eye[1]
|
|
||||||
&& hands[0] == rhs.hands[0]
|
|
||||||
&& hands[1] == rhs.hands[1]
|
|
||||||
&& view[0] == rhs.view[0]
|
|
||||||
&& view[1] == rhs.view[1]
|
|
||||||
&& head == rhs.head;
|
|
||||||
|
|
||||||
}
|
|
||||||
bool View::operator==(const View& rhs) const
|
|
||||||
{
|
|
||||||
return pose == rhs.pose && fov == rhs.fov;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& operator <<(
|
|
||||||
std::ostream& os,
|
|
||||||
const MWVR::Pose& pose)
|
|
||||||
{
|
|
||||||
os << "position=" << pose.position << ", orientation=" << pose.orientation;
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& operator <<(
|
|
||||||
std::ostream& os,
|
|
||||||
const MWVR::FieldOfView& fov)
|
|
||||||
{
|
|
||||||
os << "left=" << fov.angleLeft << ", right=" << fov.angleRight << ", down=" << fov.angleDown << ", up=" << fov.angleUp;
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& operator <<(
|
|
||||||
std::ostream& os,
|
|
||||||
const MWVR::View& view)
|
|
||||||
{
|
|
||||||
os << "pose=< " << view.pose << " >, fov=< " << view.fov << " >";
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& operator <<(
|
|
||||||
std::ostream& os,
|
|
||||||
const MWVR::PoseSet& poseSet)
|
|
||||||
{
|
|
||||||
os << "eye[" << Side::LEFT_SIDE<< "]: " << poseSet.eye[(int)Side::LEFT_SIDE] << std::endl;
|
|
||||||
os << "eye[" << Side::RIGHT_SIDE<< "]: " << poseSet.eye[(int)Side::RIGHT_SIDE] << std::endl;
|
|
||||||
os << "hands[" << Side::LEFT_SIDE<< "]: " << poseSet.hands[(int)Side::LEFT_SIDE] << std::endl;
|
|
||||||
os << "hands[" << Side::RIGHT_SIDE<< "]: " << poseSet.hands[(int)Side::RIGHT_SIDE] << std::endl;
|
|
||||||
os << "head: " << poseSet.head << std::endl;
|
|
||||||
os << "view[" << Side::LEFT_SIDE<< "]: " << poseSet.view[(int)Side::LEFT_SIDE] << std::endl;
|
|
||||||
os << "view[" << Side::RIGHT_SIDE<< "]: " << poseSet.view[(int)Side::RIGHT_SIDE] << std::endl;
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& operator <<(
|
|
||||||
std::ostream& os,
|
|
||||||
TrackedLimb limb)
|
|
||||||
{
|
|
||||||
switch (limb)
|
|
||||||
{
|
{
|
||||||
case TrackedLimb::HEAD:
|
Pose pose = *this;
|
||||||
os << "HEAD"; break;
|
pose.position += this->orientation * rhs.position;
|
||||||
case TrackedLimb::LEFT_HAND:
|
pose.orientation = rhs.orientation * this->orientation;
|
||||||
os << "LEFT_HAND"; break;
|
return pose;
|
||||||
case TrackedLimb::RIGHT_HAND:
|
|
||||||
os << "RIGHT_HAND"; break;
|
|
||||||
}
|
}
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& operator <<(
|
const Pose& Pose::operator+=(const Pose& rhs)
|
||||||
std::ostream& os,
|
|
||||||
ReferenceSpace limb)
|
|
||||||
{
|
|
||||||
switch (limb)
|
|
||||||
{
|
{
|
||||||
case ReferenceSpace::STAGE:
|
*this = *this + rhs;
|
||||||
os << "STAGE"; break;
|
return *this;
|
||||||
case ReferenceSpace::VIEW:
|
|
||||||
os << "VIEW"; break;
|
|
||||||
}
|
}
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& operator <<(
|
Pose Pose::operator*(float scalar)
|
||||||
std::ostream& os,
|
|
||||||
Side side)
|
|
||||||
{
|
|
||||||
switch (side)
|
|
||||||
{
|
{
|
||||||
case Side::LEFT_SIDE:
|
Pose pose = *this;
|
||||||
os << "LEFT_SIDE"; break;
|
pose.position *= scalar;
|
||||||
case Side::RIGHT_SIDE:
|
return pose;
|
||||||
os << "RIGHT_SIDE"; break;
|
}
|
||||||
|
|
||||||
|
const Pose& Pose::operator*=(float scalar)
|
||||||
|
{
|
||||||
|
*this = *this * scalar;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pose Pose::operator/(float scalar)
|
||||||
|
{
|
||||||
|
Pose pose = *this;
|
||||||
|
pose.position /= scalar;
|
||||||
|
return pose;
|
||||||
|
}
|
||||||
|
const Pose& Pose::operator/=(float scalar)
|
||||||
|
{
|
||||||
|
*this = *this / scalar;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Pose::operator==(const Pose& rhs) const
|
||||||
|
{
|
||||||
|
return position == rhs.position && orientation == rhs.orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldOfView::operator==(const FieldOfView& rhs) const
|
||||||
|
{
|
||||||
|
return angleDown == rhs.angleDown
|
||||||
|
&& angleUp == rhs.angleUp
|
||||||
|
&& angleLeft == rhs.angleLeft
|
||||||
|
&& angleRight == rhs.angleRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// near and far named with an underscore because of windows' headers galaxy brain defines.
|
||||||
|
osg::Matrix FieldOfView::perspectiveMatrix(float near_, float far_)
|
||||||
|
{
|
||||||
|
const float tanLeft = tanf(angleLeft);
|
||||||
|
const float tanRight = tanf(angleRight);
|
||||||
|
const float tanDown = tanf(angleDown);
|
||||||
|
const float tanUp = tanf(angleUp);
|
||||||
|
|
||||||
|
const float tanWidth = tanRight - tanLeft;
|
||||||
|
const float tanHeight = tanUp - tanDown;
|
||||||
|
|
||||||
|
const float offset = near_;
|
||||||
|
|
||||||
|
float matrix[16] = {};
|
||||||
|
|
||||||
|
matrix[0] = 2 / tanWidth;
|
||||||
|
matrix[4] = 0;
|
||||||
|
matrix[8] = (tanRight + tanLeft) / tanWidth;
|
||||||
|
matrix[12] = 0;
|
||||||
|
|
||||||
|
matrix[1] = 0;
|
||||||
|
matrix[5] = 2 / tanHeight;
|
||||||
|
matrix[9] = (tanUp + tanDown) / tanHeight;
|
||||||
|
matrix[13] = 0;
|
||||||
|
|
||||||
|
if (far_ <= near_) {
|
||||||
|
matrix[2] = 0;
|
||||||
|
matrix[6] = 0;
|
||||||
|
matrix[10] = -1;
|
||||||
|
matrix[14] = -(near_ + offset);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
matrix[2] = 0;
|
||||||
|
matrix[6] = 0;
|
||||||
|
matrix[10] = -(far_ + offset) / (far_ - near_);
|
||||||
|
matrix[14] = -(far_ * (near_ + offset)) / (far_ - near_);
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix[3] = 0;
|
||||||
|
matrix[7] = 0;
|
||||||
|
matrix[11] = -1;
|
||||||
|
matrix[15] = 0;
|
||||||
|
|
||||||
|
return osg::Matrix(matrix);
|
||||||
|
}
|
||||||
|
bool PoseSet::operator==(const PoseSet& rhs) const
|
||||||
|
{
|
||||||
|
return eye[0] == rhs.eye[0]
|
||||||
|
&& eye[1] == rhs.eye[1]
|
||||||
|
&& hands[0] == rhs.hands[0]
|
||||||
|
&& hands[1] == rhs.hands[1]
|
||||||
|
&& view[0] == rhs.view[0]
|
||||||
|
&& view[1] == rhs.view[1]
|
||||||
|
&& head == rhs.head;
|
||||||
|
|
||||||
|
}
|
||||||
|
bool View::operator==(const View& rhs) const
|
||||||
|
{
|
||||||
|
return pose == rhs.pose && fov == rhs.fov;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator <<(
|
||||||
|
std::ostream& os,
|
||||||
|
const MWVR::Pose& pose)
|
||||||
|
{
|
||||||
|
os << "position=" << pose.position << ", orientation=" << pose.orientation;
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator <<(
|
||||||
|
std::ostream& os,
|
||||||
|
const MWVR::FieldOfView& fov)
|
||||||
|
{
|
||||||
|
os << "left=" << fov.angleLeft << ", right=" << fov.angleRight << ", down=" << fov.angleDown << ", up=" << fov.angleUp;
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator <<(
|
||||||
|
std::ostream& os,
|
||||||
|
const MWVR::View& view)
|
||||||
|
{
|
||||||
|
os << "pose=< " << view.pose << " >, fov=< " << view.fov << " >";
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator <<(
|
||||||
|
std::ostream& os,
|
||||||
|
const MWVR::PoseSet& poseSet)
|
||||||
|
{
|
||||||
|
os << "eye[" << Side::LEFT_SIDE << "]: " << poseSet.eye[(int)Side::LEFT_SIDE] << std::endl;
|
||||||
|
os << "eye[" << Side::RIGHT_SIDE << "]: " << poseSet.eye[(int)Side::RIGHT_SIDE] << std::endl;
|
||||||
|
os << "hands[" << Side::LEFT_SIDE << "]: " << poseSet.hands[(int)Side::LEFT_SIDE] << std::endl;
|
||||||
|
os << "hands[" << Side::RIGHT_SIDE << "]: " << poseSet.hands[(int)Side::RIGHT_SIDE] << std::endl;
|
||||||
|
os << "head: " << poseSet.head << std::endl;
|
||||||
|
os << "view[" << Side::LEFT_SIDE << "]: " << poseSet.view[(int)Side::LEFT_SIDE] << std::endl;
|
||||||
|
os << "view[" << Side::RIGHT_SIDE << "]: " << poseSet.view[(int)Side::RIGHT_SIDE] << std::endl;
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator <<(
|
||||||
|
std::ostream& os,
|
||||||
|
TrackedLimb limb)
|
||||||
|
{
|
||||||
|
switch (limb)
|
||||||
|
{
|
||||||
|
case TrackedLimb::HEAD:
|
||||||
|
os << "HEAD"; break;
|
||||||
|
case TrackedLimb::LEFT_HAND:
|
||||||
|
os << "LEFT_HAND"; break;
|
||||||
|
case TrackedLimb::RIGHT_HAND:
|
||||||
|
os << "RIGHT_HAND"; break;
|
||||||
|
}
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator <<(
|
||||||
|
std::ostream& os,
|
||||||
|
ReferenceSpace limb)
|
||||||
|
{
|
||||||
|
switch (limb)
|
||||||
|
{
|
||||||
|
case ReferenceSpace::STAGE:
|
||||||
|
os << "STAGE"; break;
|
||||||
|
case ReferenceSpace::VIEW:
|
||||||
|
os << "VIEW"; break;
|
||||||
|
}
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator <<(
|
||||||
|
std::ostream& os,
|
||||||
|
Side side)
|
||||||
|
{
|
||||||
|
switch (side)
|
||||||
|
{
|
||||||
|
case Side::LEFT_SIDE:
|
||||||
|
os << "LEFT_SIDE"; break;
|
||||||
|
case Side::RIGHT_SIDE:
|
||||||
|
os << "RIGHT_SIDE"; break;
|
||||||
|
}
|
||||||
|
return os;
|
||||||
}
|
}
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,113 +12,109 @@
|
||||||
|
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
class OpenXRSwapchain;
|
class OpenXRSwapchain;
|
||||||
class OpenXRManager;
|
class OpenXRManager;
|
||||||
|
|
||||||
//! Describes what limb to track.
|
//! Describes what limb to track.
|
||||||
enum class TrackedLimb
|
enum class TrackedLimb
|
||||||
{
|
{
|
||||||
LEFT_HAND,
|
LEFT_HAND,
|
||||||
RIGHT_HAND,
|
RIGHT_HAND,
|
||||||
HEAD
|
HEAD
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Describes what space to track the limb in
|
//! Describes what space to track the limb in
|
||||||
enum class ReferenceSpace
|
enum class ReferenceSpace
|
||||||
{
|
{
|
||||||
STAGE = 0, //!< Track limb in the VR stage space. Meaning a space with a floor level origin and fixed horizontal orientation.
|
STAGE = 0, //!< Track limb in the VR stage space. Meaning a space with a floor level origin and fixed horizontal orientation.
|
||||||
VIEW = 1 //!< Track limb in the VR view space. Meaning a space with the head as origin and orientation.
|
VIEW = 1 //!< Track limb in the VR view space. Meaning a space with the head as origin and orientation.
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Self-descriptive
|
//! Self-descriptive
|
||||||
enum class Side
|
enum class Side
|
||||||
{
|
{
|
||||||
LEFT_SIDE = 0,
|
LEFT_SIDE = 0,
|
||||||
RIGHT_SIDE = 1
|
RIGHT_SIDE = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Represents the relative pose in space of some limb or eye.
|
//! Represents the relative pose in space of some limb or eye.
|
||||||
struct Pose
|
struct Pose
|
||||||
{
|
{
|
||||||
//! Position in space
|
//! Position in space
|
||||||
osg::Vec3 position{ 0,0,0 };
|
osg::Vec3 position{ 0,0,0 };
|
||||||
//! Orientation in space.
|
//! Orientation in space.
|
||||||
osg::Quat orientation{ 0,0,0,1 };
|
osg::Quat orientation{ 0,0,0,1 };
|
||||||
|
|
||||||
//! Add one pose to another
|
//! Add one pose to another
|
||||||
Pose operator+(const Pose& rhs);
|
Pose operator+(const Pose& rhs);
|
||||||
const Pose& operator+=(const Pose& rhs);
|
const Pose& operator+=(const Pose& rhs);
|
||||||
|
|
||||||
//! Scale a pose (does not affect orientation)
|
//! Scale a pose (does not affect orientation)
|
||||||
Pose operator*(float scalar);
|
Pose operator*(float scalar);
|
||||||
const Pose& operator*=(float scalar);
|
const Pose& operator*=(float scalar);
|
||||||
Pose operator/(float scalar);
|
Pose operator/(float scalar);
|
||||||
const Pose& operator/=(float scalar);
|
const Pose& operator/=(float scalar);
|
||||||
|
|
||||||
bool operator==(const Pose& rhs) const;
|
bool operator==(const Pose& rhs) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Fov of a single eye
|
//! Fov of a single eye
|
||||||
struct FieldOfView {
|
struct FieldOfView {
|
||||||
float angleLeft;
|
float angleLeft;
|
||||||
float angleRight;
|
float angleRight;
|
||||||
float angleUp;
|
float angleUp;
|
||||||
float angleDown;
|
float angleDown;
|
||||||
|
|
||||||
bool operator==(const FieldOfView& rhs) const;
|
bool operator==(const FieldOfView& rhs) const;
|
||||||
|
|
||||||
//! Generate a perspective matrix from this fov
|
//! Generate a perspective matrix from this fov
|
||||||
osg::Matrix perspectiveMatrix(float near, float far);
|
osg::Matrix perspectiveMatrix(float near, float far);
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Represents an eye in VR including both pose and fov. A view's pose is relative to the head.
|
//! Represents an eye in VR including both pose and fov. A view's pose is relative to the head.
|
||||||
struct View
|
struct View
|
||||||
{
|
{
|
||||||
Pose pose;
|
Pose pose;
|
||||||
FieldOfView fov;
|
FieldOfView fov;
|
||||||
bool operator==(const View& rhs) const;
|
bool operator==(const View& rhs) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
//! The complete set of poses tracked each frame by MWVR.
|
//! The complete set of poses tracked each frame by MWVR.
|
||||||
struct PoseSet
|
struct PoseSet
|
||||||
{
|
{
|
||||||
Pose eye[2]{}; //!< Stage-relative
|
Pose eye[2]{}; //!< Stage-relative
|
||||||
Pose hands[2]{}; //!< Stage-relative
|
Pose hands[2]{}; //!< Stage-relative
|
||||||
Pose head{}; //!< Stage-relative
|
Pose head{}; //!< Stage-relative
|
||||||
View view[2]{}; //!< Head-relative
|
View view[2]{}; //!< Head-relative
|
||||||
|
|
||||||
bool operator==(const PoseSet& rhs) const;
|
bool operator==(const PoseSet& rhs) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CompositionLayerProjectionView
|
struct CompositionLayerProjectionView
|
||||||
{
|
{
|
||||||
class OpenXRSwapchain* swapchain;
|
class OpenXRSwapchain* swapchain;
|
||||||
Pose pose;
|
Pose pose;
|
||||||
FieldOfView fov;
|
FieldOfView fov;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SwapchainConfig
|
struct SwapchainConfig
|
||||||
{
|
{
|
||||||
uint32_t recommendedWidth = -1;
|
uint32_t recommendedWidth = -1;
|
||||||
uint32_t maxWidth = -1;
|
uint32_t maxWidth = -1;
|
||||||
uint32_t recommendedHeight = -1;
|
uint32_t recommendedHeight = -1;
|
||||||
uint32_t maxHeight = -1;
|
uint32_t maxHeight = -1;
|
||||||
uint32_t recommendedSamples = -1;
|
uint32_t recommendedSamples = -1;
|
||||||
uint32_t maxSamples = -1;
|
uint32_t maxSamples = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Serialization methods for VR types.
|
|
||||||
std::ostream& operator <<(std::ostream& os, const Pose& pose);
|
|
||||||
std::ostream& operator <<(std::ostream& os, const FieldOfView& fov);
|
|
||||||
std::ostream& operator <<(std::ostream& os, const View& view);
|
|
||||||
std::ostream& operator <<(std::ostream& os, const PoseSet& poseSet);
|
|
||||||
std::ostream& operator <<(std::ostream& os, TrackedLimb limb);
|
|
||||||
std::ostream& operator <<(std::ostream& os, ReferenceSpace space);
|
|
||||||
std::ostream& operator <<(std::ostream& os, Side side);
|
|
||||||
|
|
||||||
|
// Serialization methods for VR types.
|
||||||
|
std::ostream& operator <<(std::ostream& os, const Pose& pose);
|
||||||
|
std::ostream& operator <<(std::ostream& os, const FieldOfView& fov);
|
||||||
|
std::ostream& operator <<(std::ostream& os, const View& view);
|
||||||
|
std::ostream& operator <<(std::ostream& os, const PoseSet& poseSet);
|
||||||
|
std::ostream& operator <<(std::ostream& os, TrackedLimb limb);
|
||||||
|
std::ostream& operator <<(std::ostream& os, ReferenceSpace space);
|
||||||
|
std::ostream& operator <<(std::ostream& os, Side side);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// I put these in the global namespace to guarantee lookup.
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,24 +1,11 @@
|
||||||
#include "vrview.hpp"
|
#include "vrview.hpp"
|
||||||
#include "vrsession.hpp"
|
|
||||||
#include "openxrmanager.hpp"
|
#include "openxrmanager.hpp"
|
||||||
#include "openxrmanagerimpl.hpp"
|
#include "openxrmanagerimpl.hpp"
|
||||||
#include "../mwinput/inputmanagerimp.hpp"
|
#include "vrsession.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
|
||||||
#include "../mwbase/world.hpp"
|
|
||||||
#include "../mwrender/renderingmanager.hpp"
|
|
||||||
#include "../mwrender/water.hpp"
|
|
||||||
#include "vrenvironment.hpp"
|
#include "vrenvironment.hpp"
|
||||||
|
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
|
||||||
|
|
||||||
#include <Windows.h>
|
|
||||||
|
|
||||||
#include <openxr/openxr.h>
|
|
||||||
#include <openxr/openxr_platform.h>
|
|
||||||
#include <openxr/openxr_platform_defines.h>
|
|
||||||
#include <openxr/openxr_reflection.h>
|
|
||||||
|
|
||||||
#include <osgViewer/Renderer>
|
#include <osgViewer/Renderer>
|
||||||
|
|
||||||
|
@ -92,8 +79,8 @@ namespace MWVR {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void VRView::UpdateSlaveCallback::updateSlave(
|
void VRView::UpdateSlaveCallback::updateSlave(
|
||||||
osg::View& view,
|
osg::View& view,
|
||||||
osg::View::Slave& slave)
|
osg::View::Slave& slave)
|
||||||
{
|
{
|
||||||
auto* camera = slave._camera.get();
|
auto* camera = slave._camera.get();
|
||||||
auto name = camera->getName();
|
auto name = camera->getName();
|
||||||
|
|
|
@ -9,7 +9,7 @@ struct XrSwapchainSubImage;
|
||||||
|
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
/// \brief Manipulates a slave camera by replacing its framebuffer with one destined for openxr
|
||||||
class VRView : public osg::Referenced
|
class VRView : public osg::Referenced
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -48,7 +48,7 @@ namespace MWVR
|
||||||
osg::Camera* createCamera(int order, const osg::Vec4& clearColor, osg::GraphicsContext* gc);
|
osg::Camera* createCamera(int order, const osg::Vec4& clearColor, osg::GraphicsContext* gc);
|
||||||
//! Get the view surface
|
//! Get the view surface
|
||||||
OpenXRSwapchain& swapchain(void) { return *mSwapchain; }
|
OpenXRSwapchain& swapchain(void) { return *mSwapchain; }
|
||||||
|
//! Present to the openxr swapchain
|
||||||
void swapBuffers(osg::GraphicsContext* gc);
|
void swapBuffers(osg::GraphicsContext* gc);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
#include "vrviewer.hpp"
|
#include "vrviewer.hpp"
|
||||||
#include "vrsession.hpp"
|
|
||||||
#include "openxrmanagerimpl.hpp"
|
#include "openxrmanagerimpl.hpp"
|
||||||
#include "openxrinput.hpp"
|
|
||||||
#include "vrenvironment.hpp"
|
#include "vrenvironment.hpp"
|
||||||
#include "Windows.h"
|
#include "vrsession.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
|
||||||
#include "../mwbase/world.hpp"
|
|
||||||
#include "../mwbase/environment.hpp"
|
|
||||||
#include "../mwworld/class.hpp"
|
|
||||||
#include "../mwworld/player.hpp"
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
|
||||||
#include "../mwrender/vismask.hpp"
|
#include "../mwrender/vismask.hpp"
|
||||||
#include <components/esm/loadrace.hpp>
|
|
||||||
#include <osg/MatrixTransform>
|
|
||||||
|
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
|
||||||
|
const std::array<const char*, 2> VRViewer::sViewNames = {
|
||||||
|
"LeftEye",
|
||||||
|
"RightEye"
|
||||||
|
};
|
||||||
|
|
||||||
VRViewer::VRViewer(
|
VRViewer::VRViewer(
|
||||||
osg::ref_ptr<osgViewer::Viewer> viewer)
|
osg::ref_ptr<osgViewer::Viewer> viewer)
|
||||||
: mRealizeOperation(new RealizeOperation())
|
: mRealizeOperation(new RealizeOperation())
|
||||||
|
@ -42,87 +39,62 @@ namespace MWVR
|
||||||
void VRViewer::realize(osg::GraphicsContext* context)
|
void VRViewer::realize(osg::GraphicsContext* context)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
|
||||||
if (mConfigured)
|
if (mConfigured)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!context->isCurrent())
|
// Give the main camera an initial draw callback that disables camera setup (we don't want it)
|
||||||
if (!context->makeCurrent())
|
|
||||||
{
|
|
||||||
throw std::logic_error("VRViewer::configure() failed to make graphics context current.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto mainCamera = mCameras["MainCamera"] = mViewer->getCamera();
|
auto mainCamera = mCameras["MainCamera"] = mViewer->getCamera();
|
||||||
mainCamera->setName("Main");
|
mainCamera->setName("Main");
|
||||||
mainCamera->setInitialDrawCallback(new VRView::InitialDrawCallback());
|
mainCamera->setInitialDrawCallback(new VRView::InitialDrawCallback());
|
||||||
|
|
||||||
osg::Vec4 clearColor = mainCamera->getClearColor();
|
|
||||||
|
|
||||||
auto* xr = Environment::get().getManager();
|
auto* xr = Environment::get().getManager();
|
||||||
if (!xr->realized())
|
if (!xr->realized())
|
||||||
xr->realize(context);
|
xr->realize(context);
|
||||||
|
|
||||||
|
// Run through initial events to start session
|
||||||
|
// For the rest of runtime this is handled by vrsession
|
||||||
xr->handleEvents();
|
xr->handleEvents();
|
||||||
|
|
||||||
|
// Small feature culling
|
||||||
|
bool smallFeatureCulling = Settings::Manager::getBool("small feature culling", "Camera");
|
||||||
|
auto smallFeatureCullingPixelSize = Settings::Manager::getFloat("small feature culling pixel size", "Camera");
|
||||||
|
osg::Camera::CullingMode cullingMode = osg::Camera::DEFAULT_CULLING | osg::Camera::FAR_PLANE_CULLING;
|
||||||
|
if (!smallFeatureCulling)
|
||||||
|
cullingMode &= ~osg::CullStack::SMALL_FEATURE_CULLING;
|
||||||
|
else
|
||||||
|
cullingMode |= osg::CullStack::SMALL_FEATURE_CULLING;
|
||||||
|
|
||||||
|
// Configure eyes, their cameras, and their enslavement.
|
||||||
|
osg::Vec4 clearColor = mainCamera->getClearColor();
|
||||||
auto config = xr->getRecommendedSwapchainConfig();
|
auto config = xr->getRecommendedSwapchainConfig();
|
||||||
|
|
||||||
auto leftView = new VRView("LeftEye", config[(int)Side::LEFT_SIDE], context->getState());
|
for (unsigned i = 0; i < sViewNames.size(); i++)
|
||||||
auto rightView = new VRView("RightEye", config[(int)Side::RIGHT_SIDE], context->getState());
|
|
||||||
|
|
||||||
mViews["LeftEye"] = leftView;
|
|
||||||
mViews["RightEye"] = rightView;
|
|
||||||
|
|
||||||
auto leftCamera = mCameras["LeftEye"] = leftView->createCamera(0, clearColor, context);
|
|
||||||
auto rightCamera = mCameras["RightEye"] = rightView->createCamera(1, clearColor, context);
|
|
||||||
|
|
||||||
leftCamera->setPreDrawCallback(mPreDraw);
|
|
||||||
rightCamera->setPreDrawCallback(mPreDraw);
|
|
||||||
|
|
||||||
leftCamera->setFinalDrawCallback(mPostDraw);
|
|
||||||
rightCamera->setFinalDrawCallback(mPostDraw);
|
|
||||||
|
|
||||||
// Stereo cameras should only draw the scene
|
|
||||||
leftCamera->setCullMask(~MWRender::Mask_GUI & ~MWRender::Mask_SimpleWater & ~MWRender::Mask_UpdateVisitor);
|
|
||||||
rightCamera->setCullMask(~MWRender::Mask_GUI & ~MWRender::Mask_SimpleWater & ~MWRender::Mask_UpdateVisitor);
|
|
||||||
|
|
||||||
leftCamera->setName("LeftEye");
|
|
||||||
rightCamera->setName("RightEye");
|
|
||||||
|
|
||||||
|
|
||||||
osg::Camera::CullingMode cullingMode = osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING;
|
|
||||||
|
|
||||||
if (!Settings::Manager::getBool("small feature culling", "Camera"))
|
|
||||||
cullingMode &= ~(osg::CullStack::SMALL_FEATURE_CULLING);
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
auto smallFeatureCullingPixelSize = Settings::Manager::getFloat("small feature culling pixel size", "Camera");
|
auto view = new VRView(sViewNames[i], config[i], context->getState());
|
||||||
leftCamera->setSmallFeatureCullingPixelSize(smallFeatureCullingPixelSize);
|
mViews[sViewNames[i]] = view;
|
||||||
rightCamera->setSmallFeatureCullingPixelSize(smallFeatureCullingPixelSize);
|
auto camera = mCameras[sViewNames[i]] = view->createCamera(i, clearColor, context);
|
||||||
cullingMode |= osg::CullStack::SMALL_FEATURE_CULLING;
|
camera->setPreDrawCallback(mPreDraw);
|
||||||
|
camera->setFinalDrawCallback(mPostDraw);
|
||||||
|
camera->setCullMask(~MWRender::Mask_GUI & ~MWRender::Mask_SimpleWater & ~MWRender::Mask_UpdateVisitor);
|
||||||
|
camera->setName(sViewNames[i]);
|
||||||
|
if (smallFeatureCulling)
|
||||||
|
camera->setSmallFeatureCullingPixelSize(smallFeatureCullingPixelSize);
|
||||||
|
camera->setCullingMode(cullingMode);
|
||||||
|
mViewer->addSlave(camera, true);
|
||||||
|
mViewer->getSlave(i)._updateSlaveCallback = new VRView::UpdateSlaveCallback(view, context);
|
||||||
|
|
||||||
|
mMsaaResolveMirrorTexture[i].reset(new VRTexture(context->getState(),
|
||||||
|
view->swapchain().width(),
|
||||||
|
view->swapchain().height(),
|
||||||
|
0));
|
||||||
}
|
}
|
||||||
leftCamera->setCullingMode(cullingMode);
|
|
||||||
rightCamera->setCullingMode(cullingMode);
|
|
||||||
|
|
||||||
mViewer->addSlave(leftCamera, true);
|
|
||||||
mViewer->addSlave(rightCamera, true);
|
|
||||||
|
|
||||||
mViewer->setReleaseContextAtEndOfFrameHint(false);
|
|
||||||
|
|
||||||
mMirrorTexture.reset(new VRTexture(context->getState(), mainCamera->getViewport()->width(), mainCamera->getViewport()->height(), 0));
|
mMirrorTexture.reset(new VRTexture(context->getState(), mainCamera->getViewport()->width(), mainCamera->getViewport()->height(), 0));
|
||||||
mMsaaResolveMirrorTexture[(int)Side::LEFT_SIDE].reset(new VRTexture(context->getState(),
|
mViewer->setReleaseContextAtEndOfFrameHint(false);
|
||||||
leftView->swapchain().width(),
|
|
||||||
leftView->swapchain().height(),
|
|
||||||
0));
|
|
||||||
mMsaaResolveMirrorTexture[(int)Side::RIGHT_SIDE].reset(new VRTexture(context->getState(),
|
|
||||||
rightView->swapchain().width(),
|
|
||||||
rightView->swapchain().height(),
|
|
||||||
0));
|
|
||||||
|
|
||||||
mViewer->getSlave(0)._updateSlaveCallback = new VRView::UpdateSlaveCallback(leftView, context);
|
|
||||||
mViewer->getSlave(1)._updateSlaveCallback = new VRView::UpdateSlaveCallback(rightView, context);
|
|
||||||
|
|
||||||
mMainCameraGC = mainCamera->getGraphicsContext();
|
mMainCameraGC = mainCamera->getGraphicsContext();
|
||||||
mMainCameraGC->setSwapCallback(new VRViewer::SwapBuffersCallback(this));
|
mMainCameraGC->setSwapCallback(new VRViewer::SwapBuffersCallback(this));
|
||||||
|
@ -132,6 +104,14 @@ namespace MWVR
|
||||||
Log(Debug::Verbose) << "Realized";
|
Log(Debug::Verbose) << "Realized";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VRView* VRViewer::getView(std::string name)
|
||||||
|
{
|
||||||
|
auto it = mViews.find(name);
|
||||||
|
if (it != mViews.end())
|
||||||
|
return it->second.get();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void VRViewer::enableMainCamera(void)
|
void VRViewer::enableMainCamera(void)
|
||||||
{
|
{
|
||||||
mCameras["MainCamera"]->setGraphicsContext(mMainCameraGC);
|
mCameras["MainCamera"]->setGraphicsContext(mMainCameraGC);
|
||||||
|
@ -150,18 +130,16 @@ namespace MWVR
|
||||||
int screenWidth = mCameras["MainCamera"]->getViewport()->width();
|
int screenWidth = mCameras["MainCamera"]->getViewport()->width();
|
||||||
int mirrorWidth = screenWidth / 2;
|
int mirrorWidth = screenWidth / 2;
|
||||||
int screenHeight = mCameras["MainCamera"]->getViewport()->height();
|
int screenHeight = mCameras["MainCamera"]->getViewport()->height();
|
||||||
const char* viewNames[] = {
|
|
||||||
"RightEye",
|
|
||||||
"LeftEye"
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; i < 2; i++)
|
for (unsigned i = 0; i < sViewNames.size(); i++)
|
||||||
{
|
{
|
||||||
auto& resolveTexture = *mMsaaResolveMirrorTexture[i];
|
auto& resolveTexture = *mMsaaResolveMirrorTexture[i];
|
||||||
resolveTexture.beginFrame(gc);
|
resolveTexture.beginFrame(gc);
|
||||||
mViews[viewNames[i]]->swapchain().renderBuffer()->blit(gc, 0, 0, resolveTexture.width(), resolveTexture.height());
|
mViews[sViewNames[i]]->swapchain().renderBuffer()->blit(gc, 0, 0, resolveTexture.width(), resolveTexture.height());
|
||||||
mMirrorTexture->beginFrame(gc);
|
mMirrorTexture->beginFrame(gc);
|
||||||
resolveTexture.blit(gc, i * mirrorWidth, 0, (i + 1) * mirrorWidth, screenHeight);
|
// Mirror the index when rendering to the mirror texture to allow cross eye mirror textures.
|
||||||
|
unsigned mirrorIndex = sViewNames.size() - 1 - i;
|
||||||
|
resolveTexture.blit(gc, mirrorIndex * mirrorWidth, 0, (mirrorIndex + 1) * mirrorWidth, screenHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
|
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
|
||||||
|
@ -206,7 +184,8 @@ namespace MWVR
|
||||||
|
|
||||||
view->postrenderCallback(info);
|
view->postrenderCallback(info);
|
||||||
|
|
||||||
// OSG will sometimes overwrite the predraw callback.
|
// This happens sometimes, i've not been able to catch it when as happens
|
||||||
|
// to see why and how i can stop it.
|
||||||
if (camera->getPreDrawCallback() != mPreDraw)
|
if (camera->getPreDrawCallback() != mPreDraw)
|
||||||
{
|
{
|
||||||
camera->setPreDrawCallback(mPreDraw);
|
camera->setPreDrawCallback(mPreDraw);
|
||||||
|
|
|
@ -14,6 +14,10 @@
|
||||||
|
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
/// \brief Manages stereo rendering and mirror texturing.
|
||||||
|
///
|
||||||
|
/// Manipulates the osgViewer by disabling main camera rendering, and instead rendering to
|
||||||
|
/// two slave cameras, each connected to and manipulated by a VRView class.
|
||||||
class VRViewer
|
class VRViewer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -65,25 +69,27 @@ namespace MWVR
|
||||||
VRViewer* mViewer;
|
VRViewer* mViewer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static const std::array<const char*, 2> sViewNames;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VRViewer(
|
VRViewer(
|
||||||
osg::ref_ptr<osgViewer::Viewer> viewer);
|
osg::ref_ptr<osgViewer::Viewer> viewer);
|
||||||
|
|
||||||
~VRViewer(void);
|
~VRViewer(void);
|
||||||
|
|
||||||
const XrCompositionLayerBaseHeader* layer();
|
|
||||||
|
|
||||||
void traversals();
|
void traversals();
|
||||||
void preDrawCallback(osg::RenderInfo& info);
|
void preDrawCallback(osg::RenderInfo& info);
|
||||||
void postDrawCallback(osg::RenderInfo& info);
|
void postDrawCallback(osg::RenderInfo& info);
|
||||||
void blitEyesToMirrorTexture(osg::GraphicsContext* gc);
|
void blitEyesToMirrorTexture(osg::GraphicsContext* gc);
|
||||||
void realize(osg::GraphicsContext* gc);
|
void realize(osg::GraphicsContext* gc);
|
||||||
bool realized() { return mConfigured; }
|
bool realized() { return mConfigured; }
|
||||||
|
VRView* getView(std::string name);
|
||||||
|
|
||||||
void enableMainCamera(void);
|
void enableMainCamera(void);
|
||||||
void disableMainCamera(void);
|
void disableMainCamera(void);
|
||||||
|
|
||||||
public:
|
private:
|
||||||
osg::ref_ptr<OpenXRManager::RealizeOperation> mRealizeOperation = nullptr;
|
osg::ref_ptr<OpenXRManager::RealizeOperation> mRealizeOperation = nullptr;
|
||||||
osg::ref_ptr<osgViewer::Viewer> mViewer = nullptr;
|
osg::ref_ptr<osgViewer::Viewer> mViewer = nullptr;
|
||||||
std::map<std::string, osg::ref_ptr<VRView> > mViews{};
|
std::map<std::string, osg::ref_ptr<VRView> > mViews{};
|
||||||
|
@ -91,7 +97,6 @@ namespace MWVR
|
||||||
osg::ref_ptr<PredrawCallback> mPreDraw{ nullptr };
|
osg::ref_ptr<PredrawCallback> mPreDraw{ nullptr };
|
||||||
osg::ref_ptr<PostdrawCallback> mPostDraw{ nullptr };
|
osg::ref_ptr<PostdrawCallback> mPostDraw{ nullptr };
|
||||||
osg::GraphicsContext* mMainCameraGC{ nullptr };
|
osg::GraphicsContext* mMainCameraGC{ nullptr };
|
||||||
//std::unique_ptr<VRTexture> mMirrorTexture{ nullptr };
|
|
||||||
std::unique_ptr<VRTexture> mMsaaResolveMirrorTexture[2]{ };
|
std::unique_ptr<VRTexture> mMsaaResolveMirrorTexture[2]{ };
|
||||||
std::unique_ptr<VRTexture> mMirrorTexture{ nullptr };
|
std::unique_ptr<VRTexture> mMirrorTexture{ nullptr };
|
||||||
|
|
||||||
|
|
|
@ -6,22 +6,6 @@
|
||||||
# OpenXR_INCLUDE_DIR, where to find openxr.h
|
# OpenXR_INCLUDE_DIR, where to find openxr.h
|
||||||
# OpenXR_VERSION, the version of the found library
|
# OpenXR_VERSION, the version of the found library
|
||||||
#
|
#
|
||||||
# This module accepts the following env variables
|
|
||||||
# OPENXR_ROOT
|
|
||||||
# This module responds to the the flag:
|
|
||||||
|
|
||||||
#=============================================================================
|
|
||||||
# Copyright 2003-2009 Kitware, Inc.
|
|
||||||
#
|
|
||||||
# Distributed under the OSI-approved BSD License (the "License");
|
|
||||||
# see accompanying file Copyright.txt for details.
|
|
||||||
#
|
|
||||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
|
||||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
# See the License for more information.
|
|
||||||
#=============================================================================
|
|
||||||
# (To distribute this file outside of CMake, substitute the full
|
|
||||||
# License text for the above reference.)
|
|
||||||
|
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
|
Loading…
Reference in a new issue