From 6474d703aec423278cbb99081f913ecc65dfad04 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 17 May 2020 16:25:51 +0200 Subject: [PATCH] GUI fixes. Animation fixes, i now understand movement accumulation better and was able to clean up some of the hacks with a better result. Lockpicks and probes now work. Haptics. Ready to be playtested. --- apps/openmw/mwbase/inputmanager.hpp | 2 + apps/openmw/mwclass/creature.cpp | 25 +- apps/openmw/mwclass/creature.hpp | 2 +- apps/openmw/mwclass/npc.cpp | 35 +- apps/openmw/mwclass/npc.hpp | 2 +- apps/openmw/mwgui/tooltips.cpp | 4 + apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- apps/openmw/mwinput/inputmanagerimp.hpp | 3 + apps/openmw/mwmechanics/character.cpp | 12 +- apps/openmw/mwmechanics/combat.cpp | 4 +- apps/openmw/mwrender/animation.cpp | 27 +- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwrender/weaponanimation.cpp | 6 +- apps/openmw/mwvr/openxrinputmanager.cpp | 263 ++++++++++---- apps/openmw/mwvr/openxrinputmanager.hpp | 3 + apps/openmw/mwvr/vranimation.cpp | 31 ++ apps/openmw/mwvr/vranimation.hpp | 8 +- apps/openmw/mwvr/vrgui.cpp | 74 ++-- apps/openmw/mwvr/vrgui.hpp | 9 +- apps/openmw/mwworld/class.cpp | 2 +- apps/openmw/mwworld/class.hpp | 5 +- apps/openmw/mwworld/projectilemanager.cpp | 3 +- apps/openmw/mwworld/worldimp.cpp | 12 +- .../myguiplatform/myguirendermanager.cpp | 4 +- files/mygui/CMakeLists.txt | 1 + files/mygui/openmw_layers_vr.xml | 1 + files/mygui/openmw_tooltips_vr.layout | 322 ++++++++++++++++++ 27 files changed, 686 insertions(+), 178 deletions(-) create mode 100644 files/mygui/openmw_tooltips_vr.layout diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 0eb06ee3d..ba1ff4aed 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -65,6 +65,8 @@ namespace MWBase virtual void enableDetectingBindingMode (int action, bool keyboard) = 0; virtual void resetToDefaultKeyBindings() = 0; virtual void resetToDefaultControllerBindings() = 0; + virtual void applyHapticsLeftHand(float intensity) = 0; + virtual void applyHapticsRightHand(float intensity) = 0; /// Returns if the last used input device was a joystick or a keyboard /// @return true if joystick, false otherwise diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index dde1c962b..2ae1ab0f0 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -14,6 +14,7 @@ #include "../mwmechanics/difficultyscaling.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/inputmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -329,11 +330,11 @@ namespace MWClass MWMechanics::diseaseContact(victim, ptr); - victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); + victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true, attackStrength); return true; } - void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const + void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful, float hitStrength) const { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); @@ -348,6 +349,8 @@ namespace MWClass if (isMobile(ptr) && !attacker.isEmpty()) setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); + bool attackerIsPlayer = attacker == MWMechanics::getPlayer(); + // Attacker and target store each other as hitattemptactor if they have no one stored yet if (!attacker.isEmpty() && attacker.getClass().isActor()) { @@ -355,20 +358,20 @@ namespace MWClass // First handle the attacked actor if ((stats.getHitAttemptActorId() == -1) && (statsAttacker.getAiSequence().isInCombat(ptr) - || attacker == MWMechanics::getPlayer())) + || attackerIsPlayer)) stats.setHitAttemptActorId(statsAttacker.getActorId()); // Next handle the attacking actor if ((statsAttacker.getHitAttemptActorId() == -1) && (statsAttacker.getAiSequence().isInCombat(ptr) - || attacker == MWMechanics::getPlayer())) + || attackerIsPlayer)) statsAttacker.setHitAttemptActorId(stats.getActorId()); } if (!object.isEmpty()) stats.setLastHitAttemptObject(object.getCellRef().getRefId()); - if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) + if (setOnPcHitMe && !attacker.isEmpty() && attackerIsPlayer) { const std::string &script = ptr.get()->mBase->mScript; /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ @@ -379,7 +382,7 @@ namespace MWClass if (!successful) { // Missed - if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer()) + if (!attacker.isEmpty() && attackerIsPlayer) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f); return; } @@ -427,6 +430,16 @@ namespace MWClass stats.setFatigue(fatigue); } } + + if(successful) + { + auto* inputManager = MWBase::Environment::get().getInputManager(); + if (attackerIsPlayer && hitStrength > 0.f) + { + float hapticIntensity = std::max(0.25f, std::min(1.f, hitStrength)); + inputManager->applyHapticsRightHand(hapticIntensity); + } + } } std::shared_ptr Creature::activate (const MWWorld::Ptr& ptr, diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 643ef9a45..3cc31bda0 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -57,7 +57,7 @@ namespace MWClass virtual bool hit(const MWWorld::Ptr& ptr, float attackStrength, int type, bool simulated) const; - virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const; + virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful, float hitStrength) const; virtual std::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 57270f6e6..510f0abd7 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -12,6 +12,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/inputmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" @@ -670,15 +671,16 @@ namespace MWClass MWMechanics::diseaseContact(victim, ptr); - othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); + othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true, attackStrength); return true; } - void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const + void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful, float hitStrength) const { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); MWMechanics::CreatureStats& stats = getCreatureStats(ptr); bool wasDead = stats.isDead(); + float rawDamage = damage; // Note OnPcHitMe is not set for friendly hits. bool setOnPcHitMe = true; @@ -689,6 +691,8 @@ namespace MWClass stats.setAttacked(true); setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); } + bool attackerIsPlayer = attacker == MWMechanics::getPlayer(); + bool victimIsPlayer = ptr == MWMechanics::getPlayer(); // Attacker and target store each other as hitattemptactor if they have no one stored yet if (!attacker.isEmpty() && attacker.getClass().isActor()) @@ -697,20 +701,20 @@ namespace MWClass // First handle the attacked actor if ((stats.getHitAttemptActorId() == -1) && (statsAttacker.getAiSequence().isInCombat(ptr) - || attacker == MWMechanics::getPlayer())) + || attackerIsPlayer)) stats.setHitAttemptActorId(statsAttacker.getActorId()); // Next handle the attacking actor if ((statsAttacker.getHitAttemptActorId() == -1) && (statsAttacker.getAiSequence().isInCombat(ptr) - || attacker == MWMechanics::getPlayer())) + || attackerIsPlayer)) statsAttacker.setHitAttemptActorId(stats.getActorId()); } if (!object.isEmpty()) stats.setLastHitAttemptObject(object.getCellRef().getRefId()); - if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) + if (setOnPcHitMe && !attacker.isEmpty() && attackerIsPlayer) { const std::string &script = getScript(ptr); /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ @@ -721,7 +725,7 @@ namespace MWClass if (!successful) { // Missed - if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer()) + if (!attacker.isEmpty() && attackerIsPlayer) sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f); return; } @@ -736,7 +740,7 @@ namespace MWClass if (damage < 0.001f) damage = 0; - bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); + bool godmode = victimIsPlayer && MWBase::Environment::get().getWorld()->getGodModeState(); if (godmode) damage = 0; @@ -860,6 +864,23 @@ namespace MWClass MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, attacker); } + + // Apply haptics + if (successful) + { + auto* inputManager = MWBase::Environment::get().getInputManager(); + if (victimIsPlayer) + { + float maxHealth = getCreatureStats(ptr).getHealth().getModified(); + float hapticIntensity = std::max(0.25f, std::min(1.f, rawDamage / ( maxHealth / 4.f))); + inputManager->applyHapticsLeftHand(hapticIntensity); + } + else if (attackerIsPlayer && hitStrength > 0.f) + { + float hapticIntensity = std::max(0.25f, std::min(1.f, hitStrength)); + inputManager->applyHapticsRightHand(hapticIntensity); + } + } } std::shared_ptr Npc::activate (const MWWorld::Ptr& ptr, diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index bb5271bb6..c3b971ea0 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -72,7 +72,7 @@ namespace MWClass virtual bool hit(const MWWorld::Ptr& ptr, float attackStrength, int type, bool simulated) const; - virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const; + virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful, float hitStrength) const; virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index b86eba651..6d0e24853 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -30,7 +30,11 @@ namespace MWGui std::string ToolTips::sSchoolNames[] = {"#{sSchoolAlteration}", "#{sSchoolConjuration}", "#{sSchoolDestruction}", "#{sSchoolIllusion}", "#{sSchoolMysticism}", "#{sSchoolRestoration}"}; ToolTips::ToolTips() : +#ifdef USE_OPENXR + Layout("openmw_tooltips_vr.layout") +#else Layout("openmw_tooltips.layout") +#endif , mFocusToolTipX(0.0) , mFocusToolTipY(0.0) , mHorizontalScrollIndex(0) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 818dd5c99..b363c07a1 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1945,7 +1945,7 @@ namespace MWGui // (Menu gets recreated next tick) if (xrMenuManager) { - xrMenuManager->showGUIs(false); + xrMenuManager->updateTracking(); xrMenuManager = nullptr; } #endif diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 167842fd8..c48b9aa35 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -239,6 +239,9 @@ namespace MWInput bool checkAllowedToUseItems() const; + void applyHapticsLeftHand(float intensity) override {}; + void applyHapticsRightHand(float intensity) override {}; + protected: void toggleMainMenu(); void toggleSpell(); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 70a007ad5..5168408d1 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -49,6 +49,11 @@ #include "actorutil.hpp" #include "spellcasting.hpp" +#ifdef USE_OPENXR +#include "../mwvr/vrenvironment.hpp" +#include "../mwvr/vranimation.hpp" +#endif + namespace { @@ -1582,9 +1587,14 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::Ptr item = *weapon; + std::string resultMessage, resultSound; // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. +#ifdef USE_OPENXR + auto* anim = MWVR::Environment::get().getPlayerAnimation(); + auto target = anim->getTarget("weapon bone"); +#else MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getFacedObject(); - std::string resultMessage, resultSound; +#endif if(!target.isEmpty()) { diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 978ca61bd..c87991a90 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -211,7 +211,7 @@ namespace MWMechanics if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) { - victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false); + victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false, attackStrength); MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker); return; } @@ -259,7 +259,7 @@ namespace MWMechanics victim.getClass().getContainerStore(victim).add(projectile, 1, victim); } - victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true); + victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true, attackStrength); } } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 2b4579308..ae597d934 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1062,29 +1062,18 @@ namespace MWRender return mNodeMap; } - // In VR, only jump, walk, and run groups should accumulate movement. - static bool vrAccum(const std::string& groupname) - { -#ifdef USE_OPENXR - if (groupname.compare(0, 4, "jump")) - if (groupname.compare(0, 4, "walk")) - if (groupname.compare(0, 3, "run")) - if (groupname.compare(0, 4, "swim")) - return false; -#else - (void)groupname; -#endif - return true; - } - static bool vrOverride(const std::string& groupname, const std::string& bone) { #ifdef USE_OPENXR + // TODO: It's difficult to design a good override system when + // I don't have a good understanding of the animation code. So for + // now i just block adding updates for nodes that should not be animated in VR. + // TODO: Some overrides cause NaN during cull. // I believe this happens if an override causes a bone to never receive // a valid matrix, but i'm not totally sure. - // Add any bone+animation pair that is messing with Vr comfort here. + // Add any bone+groupname pair that is messing with Vr comfort here. using Overrides = std::set; using GroupOverrides = std::map; static GroupOverrides sVrOverrides = @@ -1165,7 +1154,11 @@ namespace MWRender mActiveControllers.insert(std::make_pair(node, it->second)); if (blendMask == 0 && node == mAccumRoot - && (!isPlayer || vrAccum(active->first))) +#ifdef USE_OPENXR + // TODO: Little hack to keep certain animations from wobbling the camera in VR + && (!isPlayer) +#endif + ) { mAccumCtrl = it->second; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index b4d4ac664..7a0e564aa 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -442,7 +442,7 @@ public: void disable(const std::string &groupname); /** Retrieves the velocity (in units per second) that the animation will move. */ - float getVelocity(const std::string &groupname) const; + virtual float getVelocity(const std::string &groupname) const; virtual osg::Vec3f runAnimation(float duration); diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index cd44596ba..aa87edd43 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -113,10 +113,8 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) return; #ifdef USE_OPENXR - // In VR player rotation and weapon aim are unrelated. - auto* anim = MWVR::Environment::get().getPlayerAnimation(); - osg::Matrix worldMatrix = osg::computeLocalToWorld(anim->mWeaponDirectionTransform->getParentalNodePaths()[0]); - osg::Quat orient = worldMatrix.getRotate(); + // In VR weapon aim is taken from the real orientation of the weapon. + osg::Quat orient = MWVR::Environment::get().getPlayerAnimation()->getWeaponTransformMatrix().getRotate(); #else // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) diff --git a/apps/openmw/mwvr/openxrinputmanager.cpp b/apps/openmw/mwvr/openxrinputmanager.cpp index 24c33f944..f8e2b8db1 100644 --- a/apps/openmw/mwvr/openxrinputmanager.cpp +++ b/apps/openmw/mwvr/openxrinputmanager.cpp @@ -127,8 +127,6 @@ public: mOnActivate = changed && mActive; mOnDeactivate = changed && !mActive; - //if(openMWActionCode() == MWInput::InputManager::A_Journal) - Log(Debug::Verbose) << "Action[" << mXRAction.mName << "]: old=" << old << " shouldQueue=" << shouldQueue() << ", active=" << mActive << ", value=" << mValue << " onActivate=" << mOnActivate << ", mOnDeactivate=" << mOnDeactivate; if (shouldQueue()) { queue.push_back(this); @@ -287,6 +285,7 @@ struct OpenXRInput { A_XrFirst = MWInput::InputManager::A_Last, A_ActivateTouch, + A_RepositionMenu, A_XrLast }; @@ -326,6 +325,11 @@ struct OpenXRInput const Action* nextAction(); PoseSet getHandPoses(int64_t time, TrackedSpace space); + void applyHaptics(SubAction subAction, float intensity) + { + mHapticsAction.applyHaptics(subactionPath(subAction), intensity); + } + SubActionPaths mSubactionPath; XrActionSet mActionSet = XR_NULL_HANDLE; @@ -345,6 +349,7 @@ struct OpenXRInput ControllerActionPaths mTriggerValuePath; ActionPtr mGameMenu; + ActionPtr mRepositionMenu; ActionPtr mInventory; ActionPtr mActivate; ActionPtr mUse; @@ -355,18 +360,19 @@ struct OpenXRInput ActionPtr mCycleSpellRight; ActionPtr mCycleWeaponLeft; ActionPtr mCycleWeaponRight; - ActionPtr mToggleSneak; + ActionPtr mSneak; ActionPtr mQuickMenu; ActionPtr mLookLeftRight; ActionPtr mMoveForwardBackward; ActionPtr mMoveLeftRight; ActionPtr mJournal; - //OpenXRAction_Delayed mQuickSave; - - // Needed to access all the actions that don't fit on the controllers - ActionPtr mQuickKeysMenu; - + ActionPtr mQuickSave; + ActionPtr mRest; ActionPtr mActivateTouch; + ActionPtr mAlwaysRun; + ActionPtr mAutoMove; + ActionPtr mToggleHUD; + ActionPtr mToggleDebug; // Hand tracking OpenXRAction mHandPoseAction; @@ -480,6 +486,8 @@ bool OpenXRAction::getPose(XrPath subactionPath) 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; @@ -534,26 +542,31 @@ OpenXRInput::OpenXRInput() , mAPath(generateControllerActionPaths("/input/a/click")) , mBPath(generateControllerActionPaths("/input/b/click")) , mTriggerValuePath(generateControllerActionPaths("/input/trigger/value")) - , mGameMenu(std::move(createMWAction(MWInput::InputManager::A_GameMenu, "game_menu", "GameMenu", { }))) + , mGameMenu(std::move(createMWAction(MWInput::InputManager::A_GameMenu, "game_menu", "Game Menu", { }))) + , mRepositionMenu(std::move(createMWAction(A_RepositionMenu, "reposition_menu", "Reposition Menu", { }))) , mInventory(std::move(createMWAction(MWInput::InputManager::A_Inventory, "inventory", "Inventory", { }))) , mActivate(std::move(createMWAction(MWInput::InputManager::A_Activate, "activate", "Activate", { }))) - , mUse(std::move(createMWAction(MWInput::InputManager::A_Use, "use", "Use", { }))) + , mUse(std::move(createMWAction(MWInput::InputManager::A_Use, "use", "Use", { }))) , mJump(std::move(createMWAction(MWInput::InputManager::A_Jump, "jump", "Jump", { }))) , mToggleWeapon(std::move(createMWAction(MWInput::InputManager::A_ToggleWeapon, "weapon", "Weapon", { }))) , mToggleSpell(std::move(createMWAction(MWInput::InputManager::A_ToggleSpell, "spell", "Spell", { }))) - , mCycleSpellLeft(std::move(createMWAction(MWInput::InputManager::A_CycleSpellLeft, "cycle_spell_left", "CycleSpellLeft", { }))) - , mCycleSpellRight(std::move(createMWAction(MWInput::InputManager::A_CycleSpellRight, "cycle_spell_right", "CycleSpellRight", { }))) - , mCycleWeaponLeft(std::move(createMWAction(MWInput::InputManager::A_CycleWeaponLeft, "cycle_weapon_left", "CycleWeaponLeft", { }))) - , mCycleWeaponRight(std::move(createMWAction(MWInput::InputManager::A_CycleWeaponRight, "cycle_weapon_right", "CycleWeaponRight", { }))) - , mToggleSneak(std::move(createMWAction(MWInput::InputManager::A_ToggleSneak, "sneak", "Sneak", { }))) - , mQuickMenu(std::move(createMWAction(MWInput::InputManager::A_QuickMenu, "quick_menu", "QuickMenu", { }))) - , mLookLeftRight(std::move(createMWAction(MWInput::InputManager::A_LookLeftRight, "look_left_right", "LookLeftRight", { }))) - , mMoveForwardBackward(std::move(createMWAction(MWInput::InputManager::A_MoveForwardBackward, "move_forward_backward", "MoveForwardBackward", { }))) - , mMoveLeftRight(std::move(createMWAction(MWInput::InputManager::A_MoveLeftRight, "move_left_right", "MoveLeftRight", { }))) - , mJournal(std::move(createMWAction(MWInput::InputManager::A_Journal, "journal_book", "Journal Book", { }))) - //, mQuickSave(std::move(createMWAction(MWInput::InputManager::A_QuickSave, "quick_save", "Quick Save", { }))) - , mQuickKeysMenu(std::move(createMWAction(MWInput::InputManager::A_QuickKeysMenu, "quick_keys_menu", "Quick Keys Menu", { }))) + , mCycleSpellLeft(std::move(createMWAction(MWInput::InputManager::A_CycleSpellLeft, "cycle_spell_left", "Cycle Spell Left", { }))) + , mCycleSpellRight(std::move(createMWAction(MWInput::InputManager::A_CycleSpellRight, "cycle_spell_right", "Cycle Spell Right", { }))) + , mCycleWeaponLeft(std::move(createMWAction(MWInput::InputManager::A_CycleWeaponLeft, "cycle_weapon_left", "Cycle Weapon Left", { }))) + , mCycleWeaponRight(std::move(createMWAction(MWInput::InputManager::A_CycleWeaponRight, "cycle_weapon_right", "Cycle Weapon Right", { }))) + , mSneak(std::move(createMWAction(MWInput::InputManager::A_Sneak, "sneak", "Sneak", { }))) + , mQuickMenu(std::move(createMWAction(MWInput::InputManager::A_QuickMenu, "quick_menu", "Quick Menu", { }))) + , mLookLeftRight(std::move(createMWAction(MWInput::InputManager::A_LookLeftRight, "look_left_right", "Look Left Right", { }))) + , mMoveForwardBackward(std::move(createMWAction(MWInput::InputManager::A_MoveForwardBackward, "move_forward_backward", "Move Forward Backward", { }))) + , mMoveLeftRight(std::move(createMWAction(MWInput::InputManager::A_MoveLeftRight, "move_left_right", "Move Left Right", { }))) + , mJournal(std::move(createMWAction(MWInput::InputManager::A_Journal, "journal_book", "Journal Book", { }))) + , mQuickSave(std::move(createMWAction(MWInput::InputManager::A_QuickSave, "quick_save", "Quick Save", { }))) + , mRest(std::move(createMWAction(MWInput::InputManager::A_Rest, "rest", "Rest", { }))) , mActivateTouch(std::move(createMWAction(A_ActivateTouch, "activate_touched", "Activate Touch", { RIGHT_HAND }))) + , mAlwaysRun(std::move(createMWAction(MWInput::InputManager::A_AlwaysRun, "always_run", "Always Run", { }))) + , mAutoMove(std::move(createMWAction(MWInput::InputManager::A_AutoMove, "auto_move", "Auto Move", { }))) + , mToggleHUD(std::move(createMWAction(MWInput::InputManager::A_ToggleHUD, "toggle_hud", "Toggle HUD", { }))) + , mToggleDebug(std::move(createMWAction(MWInput::InputManager::A_ToggleDebug, "toggle_debug", "Toggle DEBUG", { }))) , mHandPoseAction(std::move(createXRAction(XR_ACTION_TYPE_POSE_INPUT, "hand_pose", "Hand Pose", { LEFT_HAND, RIGHT_HAND }))) , mHapticsAction(std::move(createXRAction(XR_ACTION_TYPE_VIBRATION_OUTPUT, "vibrate_hand", "Vibrate Hand", { LEFT_HAND, RIGHT_HAND }))) { @@ -562,6 +575,81 @@ OpenXRInput::OpenXRInput() XrPath oculusTouchInteractionProfilePath; CHECK_XRCMD( xrStringToPath(xr->impl().mInstance, "/interaction_profiles/oculus/touch_controller", &oculusTouchInteractionProfilePath)); + + /* + // 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 + */ + + /* + Oculus Bindings: + L-Squeeze: + Hold: Sneak + + R-Squeeze: + Hold: Enable Pointer + + L-Trigger: + Press: Jump + + R-Trigger: + IF POINTER: + Activate + ELSE: + Use + + L-Thumbstick: + X-Axis: MoveForwardBackward + Y-Axis: MoveLeftRight + Button: + Press: AlwaysRun + Long: ToggleHUD + Touch: + + R-Thumbstick: + X-Axis: LookLeftRight + Y-Axis: + Button: + Press: AutoMove + Long: ToggleDebug + Touch: + + X: + Press: Toggle Spell + Long: + Y: + Press: Rest + Long: Quick Save + A: + Press: Toggle Weapon + Long: + B: + Press: Inventory + Long: Journal + + Menu: + Press: GameMenun + Long: Reposition GUI + + */ + std::vector bindings{ { {mHandPoseAction, mPosePath[LEFT_HAND]}, {mHandPoseAction, mPosePath[RIGHT_HAND]}, @@ -574,16 +662,22 @@ OpenXRInput::OpenXRInput() {*mUse, mTriggerValuePath[RIGHT_HAND]}, {*mJump, mTriggerValuePath[LEFT_HAND]}, {*mToggleWeapon, mAPath[RIGHT_HAND]}, - {*mToggleSpell, mAPath[RIGHT_HAND]}, - {*mCycleSpellLeft, mThumbstickClickPath[LEFT_HAND]}, - {*mCycleSpellRight, mThumbstickClickPath[RIGHT_HAND]}, - {*mCycleWeaponLeft, mThumbstickClickPath[LEFT_HAND]}, - {*mCycleWeaponRight, mThumbstickClickPath[RIGHT_HAND]}, - {*mToggleSneak, mXPath[LEFT_HAND]}, + {*mToggleSpell, mXPath[LEFT_HAND]}, + //{*mCycleSpellLeft, mThumbstickClickPath[LEFT_HAND]}, + //{*mCycleSpellRight, mThumbstickClickPath[RIGHT_HAND]}, + //{*mCycleWeaponLeft, mThumbstickClickPath[LEFT_HAND]}, + //{*mCycleWeaponRight, mThumbstickClickPath[RIGHT_HAND]}, + {*mAlwaysRun, mThumbstickClickPath[LEFT_HAND]}, + {*mAutoMove, mThumbstickClickPath[RIGHT_HAND]}, + {*mToggleHUD, mThumbstickClickPath[LEFT_HAND]}, + {*mToggleDebug, mThumbstickClickPath[RIGHT_HAND]}, + {*mSneak, mSqueezeValuePath[LEFT_HAND]}, {*mInventory, mBPath[RIGHT_HAND]}, - {*mJournal, mYPath[LEFT_HAND]}, - //{*mQuickSave, mYPath[LEFT_HAND]}, + {*mRest, mYPath[LEFT_HAND]}, + {*mJournal, mBPath[RIGHT_HAND]}, + {*mQuickSave, mYPath[LEFT_HAND]}, {*mGameMenu, mMenuClickPath[LEFT_HAND]}, + {*mRepositionMenu, mMenuClickPath[LEFT_HAND]}, {*mActivateTouch, mSqueezeValuePath[RIGHT_HAND]}, } }; XrInteractionProfileSuggestedBinding suggestedBindings{ XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING }; @@ -591,24 +685,6 @@ OpenXRInput::OpenXRInput() suggestedBindings.suggestedBindings = bindings.data(); suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size(); CHECK_XRCMD(xrSuggestInteractionProfileBindings(xr->impl().mInstance, &suggestedBindings)); - - /* - mActivate; // R-Squeeze - mUse; // R-Trigger - mJump; // L-Trigger. L-trigger has value, could use this to make measured jumps ? - mToggleWeapon; // A - mToggleSpell; // A + SpellModifier - mRun; // Based on movement thumbstick value ? - mCycleSpellLeft; // L-ThumbstickClick + SpellModifier - mCycleSpellRight; // R-ThumbstickClick + SpellModifier - mCycleWeaponLeft; // L-ThumbstickClick - mCycleWeaponRight; // R-ThumbstickClick - mToggleSneak; // X - mInventory; // B - mQuickMenu; // Y - mGameMenu; // Menu - */ - } { // Set up action spaces @@ -691,6 +767,7 @@ OpenXRInput::updateControls() CHECK_XRCMD(xrSyncActions(xr->impl().mSession, &syncInfo)); mGameMenu->updateAndQueue(mActionQueue); + mRepositionMenu->updateAndQueue(mActionQueue); mInventory->updateAndQueue(mActionQueue); mActivate->updateAndQueue(mActionQueue); mUse->updateAndQueue(mActionQueue); @@ -701,15 +778,28 @@ OpenXRInput::updateControls() mCycleSpellRight->updateAndQueue(mActionQueue); mCycleWeaponLeft->updateAndQueue(mActionQueue); mCycleWeaponRight->updateAndQueue(mActionQueue); - mToggleSneak->updateAndQueue(mActionQueue); + mSneak->updateAndQueue(mActionQueue); mQuickMenu->updateAndQueue(mActionQueue); mLookLeftRight->updateAndQueue(mActionQueue); mMoveForwardBackward->updateAndQueue(mActionQueue); mMoveLeftRight->updateAndQueue(mActionQueue); mJournal->updateAndQueue(mActionQueue); - //mQuickSave->updateAndQueue(mActionQueue); - mQuickKeysMenu->updateAndQueue(mActionQueue); + mQuickSave->updateAndQueue(mActionQueue); + mRest->updateAndQueue(mActionQueue); mActivateTouch->updateAndQueue(mActionQueue); + mAlwaysRun->updateAndQueue(mActionQueue); + mAutoMove->updateAndQueue(mActionQueue); + mToggleHUD->updateAndQueue(mActionQueue); + mToggleDebug->updateAndQueue(mActionQueue); + + //if (mActivateTouch->isActive()) + //{ + // mHapticsAction.applyHaptics(mSubactionPath[RIGHT_HAND], 0.5); + //} + //if (mSneak->isActive()) + //{ + // mHapticsAction.applyHaptics(mSubactionPath[LEFT_HAND], 0.5); + //} } XrPath OpenXRInput::generateXrPath(const std::string& path) @@ -846,11 +936,11 @@ private: { injectMousePress(SDL_BUTTON_LEFT, onPress); } - //else if (onPress) - //{ - // // Other actions should only happen on release; - // return; - //} + else if (onPress) + { + // Other actions should only happen on release; + return; + } else if (dnd.mIsOnDragAndDrop) { // Intersected with the world while drag and drop is active @@ -879,6 +969,17 @@ private: mouseReleased(arg, sdlButton); } + // TODO: Configurable haptics: on/off + max intensity + void OpenXRInputManager::applyHapticsLeftHand(float intensity) + { + mXRInput->applyHaptics(OpenXRInput::LEFT_HAND, intensity); + } + + void OpenXRInputManager::applyHapticsRightHand(float intensity) + { + mXRInput->applyHaptics(OpenXRInput::RIGHT_HAND, intensity); + } + OpenXRInputManager::OpenXRInputManager( SDL_Window* window, osg::ref_ptr viewer, @@ -903,7 +1004,7 @@ private: { // VR mode has no concept of these mControlSwitch["vanitymode"] = false; - mGuiCursorEnabled = false; + //mGuiCursorEnabled = false; } OpenXRInputManager::~OpenXRInputManager() @@ -913,7 +1014,7 @@ private: void OpenXRInputManager::changeInputMode(bool mode) { // VR mode has no concept of these - mGuiCursorEnabled = false; + //mGuiCursorEnabled = false; MWInput::InputManager::changeInputMode(mode); MWBase::Environment::get().getWindowManager()->showCrosshair(false); MWBase::Environment::get().getWindowManager()->setCursorVisible(false); @@ -927,10 +1028,13 @@ private: mXRInput->updateControls(); auto* vrGuiManager = Environment::get().getGUIManager(); - vrGuiManager->updateFocus(); + bool vrHasFocus = vrGuiManager->updateFocus(); auto guiCursor = vrGuiManager->guiCursor(); - mGuiCursorX = guiCursor.x(); - mGuiCursorY = guiCursor.y(); + if (vrHasFocus) + { + mGuiCursorX = guiCursor.x(); + mGuiCursorY = guiCursor.y(); + } while (auto* action = mXRInput->nextAction()) { @@ -942,9 +1046,6 @@ private: MWInput::InputManager::update(dt, disableControls, disableEvents); bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); - auto* xrGUIManager = Environment::get().getGUIManager(); - if(xrGUIManager) - xrGUIManager->showGUIs(guiMode); setPlayerControlsEnabled(!guiMode); @@ -981,6 +1082,14 @@ private: case A_MoveForwardBackward: mInputBinder->getChannel(A_MoveForwardBackward)->setValue(-action->value() / 2.f + 0.5f); break; + case A_Sneak: + if(!mSneakToggles) + mInputBinder->getChannel(A_Sneak)->setValue(action->isActive() ? 1.f : 0.f); + break; + case A_Use: + if (!(mActivationIndication || MWBase::Environment::get().getWindowManager()->isGuiMode())) + mInputBinder->getChannel(A_Use)->setValue(action->value()); + break; default: break; } @@ -992,10 +1101,6 @@ private: { case A_GameMenu: toggleMainMenu(); - // Explicitly request position update here so that the player can move the menu - // using the menu key when the menu can't be toggled. - // TODO: This should respond to a menu HODL instead - // xrGUIManager->updateTracking(); break; case A_Screenshot: screenshot(); @@ -1058,9 +1163,11 @@ private: showQuickKeysMenu(); break; case A_ToggleHUD: + Log(Debug::Verbose) << "Toggle HUD"; MWBase::Environment::get().getWindowManager()->toggleHud(); break; case A_ToggleDebug: + Log(Debug::Verbose) << "Toggle Debug"; MWBase::Environment::get().getWindowManager()->toggleDebugWindow(); break; case A_QuickSave: @@ -1085,27 +1192,33 @@ private: if (checkAllowedToUseItems() && MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) MWBase::Environment::get().getWindowManager()->cycleWeapon(true); break; - case A_ToggleSneak: - toggleSneaking(); - break; case A_Jump: mAttemptJump = true; break; + case OpenXRInput::A_RepositionMenu: + xrGUIManager->updateTracking(); + break; + case A_Use: + if (mActivationIndication || MWBase::Environment::get().getWindowManager()->isGuiMode()) + pointActivation(true); default: break; } } - // A few actions need both activate and deactivate + // A few actions need to fire on deactivation if (action->onDeactivate()) { switch (action->openMWActionCode()) { case A_Use: + mInputBinder->getChannel(A_Use)->setValue(0.f); if (mActivationIndication || MWBase::Environment::get().getWindowManager()->isGuiMode()) - pointActivation(action->onActivate()); - else - mInputBinder->getChannel(A_Use)->setValue(action->isActive()); + pointActivation(false); + break; + case A_Sneak: + if (mSneakToggles) + toggleSneaking(); break; default: break; diff --git a/apps/openmw/mwvr/openxrinputmanager.hpp b/apps/openmw/mwvr/openxrinputmanager.hpp index baa0b2368..75438261c 100644 --- a/apps/openmw/mwvr/openxrinputmanager.hpp +++ b/apps/openmw/mwvr/openxrinputmanager.hpp @@ -49,6 +49,9 @@ namespace MWVR void injectMousePress(int sdlButton, bool onPress); + void applyHapticsLeftHand(float intensity) override; + void applyHapticsRightHand(float intensity) override; + std::unique_ptr mXRInput; std::unique_ptr mRealisticCombat; Pose mPreviousHeadPose{}; diff --git a/apps/openmw/mwvr/vranimation.cpp b/apps/openmw/mwvr/vranimation.cpp index 97f03c772..24fe76020 100644 --- a/apps/openmw/mwvr/vranimation.cpp +++ b/apps/openmw/mwvr/vranimation.cpp @@ -507,6 +507,8 @@ void VRAnimation::updateParts() removeIndividualPart(ESM::PartReferenceType::PRT_RAnkle); removeIndividualPart(ESM::PartReferenceType::PRT_LKnee); removeIndividualPart(ESM::PartReferenceType::PRT_RKnee); + removeIndividualPart(ESM::PartReferenceType::PRT_LFoot); + removeIndividualPart(ESM::PartReferenceType::PRT_RFoot); } else { @@ -613,6 +615,11 @@ osg::ref_ptr VRAnimation::createPointerGeometry(void) return geometry; } +float VRAnimation::getVelocity(const std::string& groupname) const +{ + return 0.0f; +} + osg::Vec3f VRAnimation::runAnimation(float timepassed) { return NpcAnimation::runAnimation(timepassed); @@ -662,6 +669,13 @@ void VRAnimation::addControllers() group->addChild(mModelOffset); mModelOffset->addChild(mObjectRoot); } + + auto wb = mNodeMap.find("weapon bone"); + if (wb != mNodeMap.end()) + { + wb->second->removeChild(mWeaponPointerTransform); + wb->second->addChild(mWeaponPointerTransform); + } } void VRAnimation::enableHeadAnimation(bool) { @@ -692,6 +706,23 @@ const MWRender::RayResult& VRAnimation::getPointerTarget() const return mPointerTarget; } + +MWWorld::Ptr VRAnimation::getTarget(const std::string& directorNode) +{ + auto node = mNodeMap.find(directorNode); + auto* world = MWBase::Environment::get().getWorld(); + MWRender::RayResult result{}; + if (node != mNodeMap.end()) + if (world) + world->getTargetObject(result, node->second); + return result.mHitObject; +} + +osg::Matrix VRAnimation::getWeaponTransformMatrix() const +{ + return osg::computeLocalToWorld(mWeaponDirectionTransform->getParentalNodePaths()[0]); +} + void VRAnimation::updatePointerTarget() { auto* world = MWBase::Environment::get().getWorld(); diff --git a/apps/openmw/mwvr/vranimation.hpp b/apps/openmw/mwvr/vranimation.hpp index f4d6dcabf..ccf105ee1 100644 --- a/apps/openmw/mwvr/vranimation.hpp +++ b/apps/openmw/mwvr/vranimation.hpp @@ -63,9 +63,15 @@ public: void updatePointerTarget(); -public: + MWWorld::Ptr getTarget(const std::string& directorNode); + + osg::Matrix getWeaponTransformMatrix() const; + +protected: static osg::ref_ptr createPointerGeometry(void); + float getVelocity(const std::string& groupname) const override; + public: std::shared_ptr mSession; osg::ref_ptr mForearmControllers[2]; diff --git a/apps/openmw/mwvr/vrgui.cpp b/apps/openmw/mwvr/vrgui.cpp index e671d4685..c5be28cd2 100644 --- a/apps/openmw/mwvr/vrgui.cpp +++ b/apps/openmw/mwvr/vrgui.cpp @@ -134,11 +134,11 @@ private: VRGUILayer::VRGUILayer( osg::ref_ptr geometryRoot, osg::ref_ptr cameraRoot, - std::string filter, + std::string layerName, LayerConfig config, VRGUIManager* parent) : mConfig(config) - , mFilter(filter) + , mLayerName(layerName) , mGeometryRoot(geometryRoot) , mCameraRoot(cameraRoot) { @@ -176,6 +176,9 @@ VRGUILayer::VRGUILayer( mGeometry->setName("VRGUILayer"); // Create the camera that will render the menu texture + std::string filter = mLayerName; + if (!mConfig.extraLayers.empty()) + filter = filter + ";" + mConfig.extraLayers; mGUICamera = new GUICamera(config.pixelResolution.x(), config.pixelResolution.y(), config.backgroundColor); osgMyGUI::RenderManager& renderManager = static_cast(MyGUI::RenderManager::getInstance()); mMyGUICamera = renderManager.createGUICamera(osg::Camera::NESTED_RENDER, filter); @@ -201,8 +204,10 @@ VRGUILayer::VRGUILayer( mCameraRoot->addChild(mGUICamera); // Edit offset to account for priority - if(!mConfig.sideBySide) + if (!mConfig.sideBySide) + { mConfig.offset.y() -= 0.001f * mConfig.priority; + } mTransform->addUpdateCallback(new LayerUpdateCallback(this)); } @@ -302,7 +307,7 @@ void VRGUILayer::updateRect() } // Some widgets don't capture the full visual - if (mFilter == "JournalBooks" || mFilter == "MessageBox" ) + if (mLayerName == "JournalBooks" ) { mRealRect.left = 0.f; mRealRect.top = 0.f; @@ -310,7 +315,7 @@ void VRGUILayer::updateRect() mRealRect.bottom = 1.f; } - if (mFilter == "Notification") + if (mLayerName == "Notification") { // The latest widget for notification is always the top one // So we just have to stretch the rectangle to the bottom @@ -357,7 +362,7 @@ void VRGUILayer::update() { mTransform->setScale(osg::Vec3(w / res, 1.f, h / res)); } - if (mFilter == "Notification") + if (mLayerName == "Notification") { auto viewSize = MyGUI::RenderManager::getInstance().getViewSize(); h = (1.f - mRealRect.top) * viewSize.height; @@ -421,44 +426,28 @@ VRGUIManager::~VRGUIManager(void) { } -void VRGUIManager::showGUIs(bool show) -{ -} - -static const LayerConfig createDefaultConfig(int priority) +static const LayerConfig createDefaultConfig(int priority, bool background = true, SizingMode sizingMode = SizingMode::Auto) { return LayerConfig{ - 1, + priority, false, // side-by-side - osg::Vec4{0.f,0.f,0.f,.75f}, // background + background ? osg::Vec4{0.f,0.f,0.f,.75f} : osg::Vec4{}, // background osg::Vec3(0.f,0.66f,-.25f), // offset osg::Vec2(0.f,0.f), // center (model space) osg::Vec2(1.f, 1.f), // extent (meters) 1024, // Spatial resolution (pixels per meter) osg::Vec2i(2048,2048), // Texture resolution osg::Vec2(1,1), - SizingMode::Auto, - TrackingMode::Menu + sizingMode, + TrackingMode::Menu, + "Popup" }; } LayerConfig gDefaultConfig = createDefaultConfig(1); -LayerConfig gJournalBooksConfig = LayerConfig -{ - 2, - gDefaultConfig.sideBySide, - osg::Vec4{}, // background - gDefaultConfig.offset, - gDefaultConfig.center, - gDefaultConfig.extent, - gDefaultConfig.spatialResolution, - gDefaultConfig.pixelResolution, - gDefaultConfig.myGUIViewSize, - SizingMode::Fixed, - gDefaultConfig.trackingMode -}; -LayerConfig gDefaultWindowsConfig = createDefaultConfig(3); -LayerConfig gMessageBoxConfig = gJournalBooksConfig; -LayerConfig gNotificationConfig = gJournalBooksConfig; +LayerConfig gJournalBooksConfig = createDefaultConfig(2, false, SizingMode::Fixed); +LayerConfig gDefaultWindowsConfig = createDefaultConfig(3, true); +LayerConfig gMessageBoxConfig = createDefaultConfig(6, false, SizingMode::Auto);; +LayerConfig gNotificationConfig = createDefaultConfig(7, false, SizingMode::Fixed);; static const float sSideBySideRadius = 1.f; static const float sSideBySideAzimuthInterval = -osg::PI_4; @@ -475,7 +464,8 @@ static const LayerConfig createSideBySideConfig(int priority) gDefaultConfig.pixelResolution, osg::Vec2(0.70f, 0.70f), SizingMode::Fixed, - gDefaultConfig.trackingMode + gDefaultConfig.trackingMode, + "" }; }; @@ -499,6 +489,7 @@ LayerConfig gStatusHUDConfig = LayerConfig gDefaultConfig.myGUIViewSize, SizingMode::Auto, TrackingMode::HudLeftHand, + "" }; LayerConfig gPopupConfig = LayerConfig @@ -514,6 +505,7 @@ LayerConfig gPopupConfig = LayerConfig gDefaultConfig.myGUIViewSize, SizingMode::Auto, TrackingMode::HudRightHand, + "" }; @@ -521,8 +513,7 @@ LayerConfig gPopupConfig = LayerConfig static std::map gLayerConfigs = { {"StatusHUD", gStatusHUDConfig}, - //{"MinimapHUD", gMinimapHUDConfig}, - {"Popup", gPopupConfig}, + {"Tooltip", gPopupConfig}, {"JournalBooks", gJournalBooksConfig}, {"InventoryCompanionWindow", gInventoryCompanionWindowConfig}, {"InventoryWindow", gInventoryWindowConfig}, @@ -532,13 +523,13 @@ static std::map gLayerConfigs = {"DialogueWindow", gDialogueWindowConfig}, {"MessageBox", gMessageBoxConfig}, {"Windows", gDefaultWindowsConfig}, - {"Notification", gNotificationConfig} + {"Notification", gNotificationConfig}, }; static std::set layerBlacklist = { "Overlay", - "AdditiveOverlay" + "AdditiveOverlay", }; void VRGUIManager::updateSideBySideLayers() @@ -582,9 +573,6 @@ void VRGUIManager::insertLayer(const std::string& name) layer->mGeometry->setUserData(new VRGUILayerUserData(mLayers[name])); - // Default new layer's pick to false - // TODO: re-add widget->setLayerPick(false) somewhere; - if (config.sideBySide) { mSideBySideLayers.push_back(layer); @@ -715,7 +703,7 @@ void VRGUIManager::updateTracking(void) layer.second->updateTracking(mHeadPose); } -void VRGUIManager::updateFocus() +bool VRGUIManager::updateFocus() { auto* anim = MWVR::Environment::get().getPlayerAnimation(); if (anim && anim->mPointerTarget.mHit) @@ -728,12 +716,14 @@ void VRGUIManager::updateFocus() newFocusLayer = userData->mLayer.lock(); } - if (newFocusLayer && newFocusLayer->mFilter != "Notification") + if (newFocusLayer && newFocusLayer->mLayerName != "Notification") { setFocusLayer(newFocusLayer.get()); computeGuiCursor(anim->mPointerTarget.mHitPointLocal); + return true; } } + return false; } void VRGUIManager::setFocusLayer(VRGUILayer* layer) diff --git a/apps/openmw/mwvr/vrgui.hpp b/apps/openmw/mwvr/vrgui.hpp index c53e522b3..4632b2541 100644 --- a/apps/openmw/mwvr/vrgui.hpp +++ b/apps/openmw/mwvr/vrgui.hpp @@ -61,6 +61,7 @@ namespace MWVR osg::Vec2 myGUIViewSize; //!< Resizable elements are resized to this (fraction of full view) SizingMode sizingMode; //!< How to size the layer TrackingMode trackingMode; //!< Tracking mode + std::string extraLayers; //!< Additional layers to draw (list separated by any non-alphabetic) bool operator<(const LayerConfig& rhs) const { return priority < rhs.priority; } }; @@ -71,7 +72,7 @@ namespace MWVR VRGUILayer( osg::ref_ptr geometryRoot, osg::ref_ptr cameraRoot, - std::string filter, + std::string layerName, LayerConfig config, VRGUIManager* parent); ~VRGUILayer(); @@ -95,7 +96,7 @@ namespace MWVR public: Pose mTrackedPose{}; LayerConfig mConfig; - std::string mFilter; + std::string mLayerName; std::vector mWidgets; osg::ref_ptr mGeometryRoot; osg::ref_ptr mGeometry{ new osg::Geometry }; @@ -123,8 +124,6 @@ namespace MWVR ~VRGUIManager(void); - void showGUIs(bool show); - void setVisible(MWGui::Layout*, bool visible); void updateSideBySideLayers(); @@ -139,7 +138,7 @@ namespace MWVR void updateTracking(void); - void updateFocus(); + bool updateFocus(); void setFocusLayer(VRGUILayer* layer); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 8d763a59e..77b44270e 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -109,7 +109,7 @@ namespace MWWorld throw std::runtime_error("class cannot block"); } - void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const + void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, float hitStrength) const { throw std::runtime_error("class cannot be hit"); } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 825ccd4d5..687286280 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -131,11 +131,12 @@ namespace MWWorld /// @return True if the attack had a victim, regardless if hit was successful or not. /// (default implementation: throw an exception) - virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const; + virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful, float hitStrength = 0.f) const; ///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is /// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the /// actor responsible for the attack, and \a successful specifies if the hit is - /// successful or not. + /// successful or not. \a hitStrength is the fraction of max attack strength applied, and is + /// used to determine haptic feedback intensity. virtual void block (const Ptr& ptr) const; ///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 562ba6976..f5140e11b 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -277,8 +277,7 @@ namespace MWWorld #ifdef USE_OPENXR if (caster == MWBase::Environment::get().getWorld()->getPlayerPtr()) { - auto* anim = MWVR::Environment::get().getPlayerAnimation(); - osg::Matrix worldMatrix = osg::computeLocalToWorld(anim->mWeaponDirectionTransform->getParentalNodePaths()[0]); + osg::Matrix worldMatrix = MWVR::Environment::get().getPlayerAnimation()->getWeaponTransformMatrix(); orient = worldMatrix.getRotate(); pos = worldMatrix.getTrans(); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bffb6e9bd..7e8e2d8b9 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1101,7 +1101,8 @@ namespace MWWorld MWWorld::Ptr World::getFacedObject() { #ifdef USE_OPENXR - + // TODO: Rename this method to getTargetObject? + // "getFacedObject" doesn't make sense with finger pointing. auto* anim = MWVR::Environment::get().getPlayerAnimation(); if (anim && anim->mPointerTarget.mHit) return anim->mPointerTarget.mHitObject; @@ -1163,12 +1164,8 @@ namespace MWWorld if (ptr == getPlayerPtr()) { #ifdef USE_OPENXR - // TODO: Configurable realistic fighting ? - // Use current aim of weapon to impact - // TODO: Use bounding box of weapon instead ? - auto* anim = MWVR::Environment::get().getPlayerAnimation(); - osg::Matrix worldMatrix = osg::computeLocalToWorld(anim->mWeaponDirectionTransform->getParentalNodePaths()[0]); + osg::Matrix worldMatrix = MWVR::Environment::get().getPlayerAnimation()->getWeaponTransformMatrix(); auto result = mPhysics->getHitContact(ptr, worldMatrix.getTrans(), worldMatrix.getRotate(), distance, targets); if (!result.first.isEmpty()) @@ -3110,8 +3107,7 @@ namespace MWWorld #ifdef USE_OPENXR if (actor == MWMechanics::getPlayer()) { - auto* anim = MWVR::Environment::get().getPlayerAnimation(); - osg::Matrix worldMatrix = osg::computeLocalToWorld(anim->mWeaponDirectionTransform->getParentalNodePaths()[0]); + osg::Matrix worldMatrix = MWVR::Environment::get().getPlayerAnimation()->getWeaponTransformMatrix(); origin = worldMatrix.getTrans(); orient = worldMatrix.getRotate(); } diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index 930d58f7d..e76446610 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -563,8 +563,10 @@ void GUICamera::collectDrawCalls(std::string filter) auto layer = myGUILayers->getLayer(i); auto name = layer->getName(); - if (name == filter) + if (filter.find(name) != std::string::npos) + { layer->renderToTarget(this, mUpdate); + } } } end(); diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 22890f190..24b8cc86c 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -92,6 +92,7 @@ set(MYGUI_FILES openmw_text.skin.xml openmw_text_input.layout openmw_tooltips.layout + openmw_tooltips_vr.layout openmw_trade_window.layout openmw_trade_window_vr.layout openmw_trainingwindow.layout diff --git a/files/mygui/openmw_layers_vr.xml b/files/mygui/openmw_layers_vr.xml index 8b314d67f..4deecd67b 100644 --- a/files/mygui/openmw_layers_vr.xml +++ b/files/mygui/openmw_layers_vr.xml @@ -19,6 +19,7 @@ + diff --git a/files/mygui/openmw_tooltips_vr.layout b/files/mygui/openmw_tooltips_vr.layout new file mode 100644 index 000000000..3612d0e43 --- /dev/null +++ b/files/mygui/openmw_tooltips_vr.layout @@ -0,0 +1,322 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +