diff --git a/CMakeLists.txt b/CMakeLists.txt index e51925b09..0cd8a6db1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,7 +240,7 @@ if (WIN32) # Get rid of useless crud from windows.h add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) - # Get rid of the nonsense warning "enum type is unscoped" + # Get rid of the pointless warning "enum type is unscoped" set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd26812") endif() diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 5cbba33c6..471be77d9 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -647,7 +647,12 @@ namespace MWBase virtual float getTargetObject(MWRender::RayResult& result, osg::Transform* pointer) = 0; virtual float getTargetObject(MWRender::RayResult& result, osg::Transform* pointer, float maxDistance, bool ignorePlayer) = 0; + /// @Return ESM::Weapon::Type enum describing the type of weapon currently drawn by the player. + virtual int getActiveWeaponType(void) = 0; + virtual MWPhysics::PhysicsSystem* getPhysicsSystem(void) = 0; + + virtual void toggleWaterRTT(bool enable) = 0; }; } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index fcd34a12f..ec85d64d6 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -48,10 +48,6 @@ #include "actorutil.hpp" #include "spellcasting.hpp" -#ifdef USE_OPENXR -#include "../mwvr/vranimation.hpp" -#endif - namespace { diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 0575c8a1c..736474003 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -454,7 +454,6 @@ namespace MWPhysics osg::Vec3 moved = newPosition - position; inputManager->mHeadOffset.x() -= moved.x(); inputManager->mHeadOffset.y() -= moved.y(); - } #endif diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3505ea261..5280b1123 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -43,6 +43,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority +#include "../mwmechanics/actorutil.hpp" #include "vismask.hpp" #include "util.hpp" @@ -895,6 +896,13 @@ namespace MWRender return; } + + const bool isPlayer = (mPtr == MWMechanics::getPlayer()); + if (isPlayer) + { + Log(Debug::Verbose) << "groupname=" << groupname << ", start=" << start << ", stop=" << stop << ", accumRoot=" << mAccumRoot->getName(); + } + AnimStateMap::iterator stateiter = mStates.begin(); while(stateiter != mStates.end()) { @@ -1053,8 +1061,65 @@ 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")) + return false; +#else + (void)groupname; +#endif + return true; + } + + static bool vrOverride(const std::string& groupname, const std::string& bone) + { +#ifdef USE_OPENXR + // 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. + using Overrides = std::set; + using GroupOverrides = std::map; + static GroupOverrides sVrOverrides = + { + { + "crossbow", + { + "weapon bone" + } + }, + { + "throwweapon", + { + "weapon bone" + } + }, + }; + + bool override = false; + auto find = sVrOverrides.find(groupname); + if (find != sVrOverrides.end()) + { + override = !!find->second.count(bone); + } + + return override; +#else + (void)bone; + (void)groupname; + return false; +#endif + } + void Animation::resetActiveGroups() { + const bool isPlayer = (mPtr == MWMechanics::getPlayer()); + // remove all previous external controllers from the scene graph for (ControllerMap::iterator it = mActiveControllers.begin(); it != mActiveControllers.end(); ++it) { @@ -1093,11 +1158,12 @@ namespace MWRender for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it) { osg::ref_ptr node = getNodeMap().at(it->first); // this should not throw, we already checked for the node existing in addAnimSource - - node->addUpdateCallback(it->second); + if(!isPlayer || !vrOverride(active->first, it->first)) + node->addUpdateCallback(it->second); mActiveControllers.insert(std::make_pair(node, it->second)); - if (blendMask == 0 && node == mAccumRoot) + if (blendMask == 0 && node == mAccumRoot + && (!isPlayer || vrAccum(active->first))) { mAccumCtrl = it->second; diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 73f153f66..b78403771 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -163,10 +163,6 @@ namespace MWRender setYaw(yaw); setPitch(pitch); setRoll(roll); - - // This might happen mid-update traversal because of openxr input management. - // It is essential to VR comfort that this be effective immediately and not next frame. - updateCamera(); } void Camera::attachTo(const MWWorld::Ptr &ptr) @@ -431,7 +427,7 @@ namespace MWRender mTrackingNode = findRootVisitor.mFoundNode; if (!mTrackingNode) - throw std::logic_error("Unapple to find tracking node for VR camera"); + throw std::logic_error("Unable to find tracking node for VR camera"); mHeightScale = 1.f; #else if(isFirstPerson()) @@ -452,7 +448,7 @@ namespace MWRender else mHeightScale = 1.f; } - rotateCamera(getPitch(), getYaw(), false); + rotateCamera(getPitch(), 0.f, getYaw(), false); #endif } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 0e438ac5b..c86110377 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -36,9 +36,7 @@ public: VM_Normal, VM_FirstPerson, VM_HeadOnly, -#ifdef USE_OPENXR VM_VRHeadless, -#endif }; protected: diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c07b68880..503a79b1f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1093,7 +1093,7 @@ namespace MWRender { osg::Matrix worldMatrix = osg::computeLocalToWorld(source->getParentalNodePaths()[0]); - osg::Vec3f direction = worldMatrix.getRotate() * osg::Vec3f(1, 0, 0); + osg::Vec3f direction = worldMatrix.getRotate() * osg::Vec3f(0, 1, 0); direction.normalize(); osg::Vec3f raySource = worldMatrix.getTrans(); @@ -1469,6 +1469,11 @@ namespace MWRender mNavMeshNumber = value; } + void RenderingManager::toggleWaterRTT(bool enable) + { + mWater->toggleRTT(enable); + } + void RenderingManager::updateNavMesh() { if (!mNavMesh->isEnabled()) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 3574656f0..d782eafc1 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -242,6 +242,8 @@ namespace MWRender void setNavMeshNumber(const std::size_t value); + void toggleWaterRTT(bool enable); + private: void updateProjectionMatrix(); void updateTextureFiltering(); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 6d230d36e..e46f2b974 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -436,6 +436,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem , mToggled(true) , mTop(0) , mInterior(false) + , mRTTToggled(true) { mSimulation.reset(new RippleSimulation(mSceneRoot, resourceSystem)); @@ -720,6 +721,20 @@ bool Water::toggle() return mToggled; } +void Water::toggleRTT(bool enable) +{ + mRTTToggled = enable; + bool visible = mEnabled && mToggled && mRTTToggled; + // The idea here is to stop RTT from happening on one eye to save performance + // This didn't work, though + //{ + // if (mRefraction) + // mRefraction->setNodeMask(visible ? Mask_RenderToTexture : 0); + // if (mReflection) + // mReflection->setNodeMask(visible ? Mask_RenderToTexture : 0); + //} +} + bool Water::isUnderwater(const osg::Vec3f &pos) const { return pos.z() < mTop && mToggled && mEnabled; diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 5d99413c6..fc9435197 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -71,6 +71,8 @@ namespace MWRender float mTop; bool mInterior; + bool mRTTToggled; + osg::Vec3f getSceneNodeCoordinates(int gridX, int gridY); void updateVisible(); @@ -94,6 +96,9 @@ namespace MWRender bool toggle(); + /// Call before each eye to allow rendering water only once per frame in VR + void toggleRTT(bool enable); + bool isUnderwater(const osg::Vec3f& pos) const; /// adds an emitter, position will be tracked automatically using its scene node diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 24c92bc32..cd44596ba 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -17,6 +17,11 @@ #include "../mwmechanics/combat.hpp" #include "../mwmechanics/weapontype.hpp" +#ifdef USE_OPENXR +#include "../mwvr/vrenvironment.hpp" +#include "../mwvr/vranimation.hpp" +#endif + #include "animation.hpp" #include "rotatecontroller.hpp" @@ -107,9 +112,16 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) if (weapon->getTypeName() != typeid(ESM::Weapon).name()) 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(); +#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)) * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); +#endif const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); diff --git a/apps/openmw/mwvr/openxrinputmanager.cpp b/apps/openmw/mwvr/openxrinputmanager.cpp index 7a75b3468..a5c108c29 100644 --- a/apps/openmw/mwvr/openxrinputmanager.cpp +++ b/apps/openmw/mwvr/openxrinputmanager.cpp @@ -760,11 +760,16 @@ void OpenXRInputManager::updateActivationIndication(void) bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); bool show = guiMode | mActivationIndication; if (mPlayer) - mPlayer->setPointing(show); + { + if (show != mPlayer->getPointing()) + { + mPlayer->setPointing(show); - auto* playerAnimation = Environment::get().getPlayerAnimation(); - if (playerAnimation) - playerAnimation->setPointForward(show); + auto* playerAnimation = Environment::get().getPlayerAnimation(); + if (playerAnimation) + playerAnimation->setPointForward(show); + } + } } @@ -938,8 +943,11 @@ private: auto player = mPlayer->getPlayer(); if (!mRealisticCombat || mRealisticCombat->ptr != player) mRealisticCombat.reset(new RealisticCombat::StateMachine(player)); - mRealisticCombat->update(dt, !guiMode); + bool enabled = !guiMode && mPlayer->getDrawState() == MWMechanics::DrawState_Weapon; + mRealisticCombat->update(dt, enabled); } + + updateHead(); } void OpenXRInputManager::processEvent(const OpenXRActionEvent& event) @@ -1112,7 +1120,7 @@ private: auto* xr = Environment::get().getManager(); auto* session = Environment::get().getSession(); - auto currentHeadPose = session->predictedPoses().head[(int)TrackedSpace::STAGE]; + auto currentHeadPose = session->predictedPoses(OpenXRSession::PredictionSlice::Predraw).head[(int)TrackedSpace::STAGE]; xr->playerScale(currentHeadPose); currentHeadPose.position *= Environment::get().unitsPerMeter(); osg::Vec3 vrMovement = currentHeadPose.position - mPreviousHeadPose.position; @@ -1141,8 +1149,6 @@ private: mVrAngles[1] = roll; mVrAngles[2] = yaw; world->rotateObject(player, mVrAngles[0], mVrAngles[1], mVrAngles[2], MWBase::RotationFlag_none); - - MWBase::Environment::get().getWorld()->getRenderingManager().getCamera()->updateCamera(); } } diff --git a/apps/openmw/mwvr/openxrsession.cpp b/apps/openmw/mwvr/openxrsession.cpp index 8cd996340..eb5912587 100644 --- a/apps/openmw/mwvr/openxrsession.cpp +++ b/apps/openmw/mwvr/openxrsession.cpp @@ -48,34 +48,49 @@ namespace MWVR auto* xr = Environment::get().getManager(); - if (!xr->sessionRunning()) + if (!mShouldRender) return; - if (!mPredictionsReady) + + if (mRenderedFrames != mRenderFrame - 1) + { + Log(Debug::Warning) << "swapBuffers called out of order"; + waitFrame(); return; + } for (auto layer : mLayerStack.layerObjects()) if(layer) layer->swapBuffers(gc); timer.checkpoint("Rendered"); - xr->endFrame(xr->impl().frameState().predictedDisplayTime, &mLayerStack); + mRenderedFrames++; + } + + const PoseSets& OpenXRSession::predictedPoses(PredictionSlice slice) + { + if(slice == PredictionSlice::Predraw) + waitFrame(); + return mPredictedPoses[(int)slice]; } void OpenXRSession::waitFrame() { - auto* xr = Environment::get().getManager(); - xr->handleEvents(); - if (!xr->sessionRunning()) - return; + if(mPredictedFrames < mPredictionFrame) + { + Timer timer("OpenXRSession::waitFrame"); + auto* xr = Environment::get().getManager(); + xr->handleEvents(); + mIsRunning = xr->sessionRunning(); + if (!mIsRunning) + return; - Timer timer("OpenXRSession::waitFrame"); - xr->waitFrame(); - timer.checkpoint("waitFrame"); - predictNext(0); - - mPredictionsReady = true; + xr->waitFrame(); + timer.checkpoint("waitFrame"); + predictNext(0); + mPredictedFrames++; + } } @@ -118,7 +133,7 @@ namespace MWVR float OpenXRSession::movementYaw(void) { - auto lhandquat = predictedPoses().hands[(int)TrackedSpace::VIEW][(int)MWVR::Side::LEFT_HAND].orientation; + auto lhandquat = predictedPoses(PredictionSlice::Predraw).hands[(int)TrackedSpace::VIEW][(int)MWVR::Side::LEFT_HAND].orientation; float yaw = 0.f; float pitch = 0.f; float roll = 0.f; @@ -126,25 +141,42 @@ namespace MWVR return yaw; } + void OpenXRSession::advanceFrame(void) + { + auto* xr = Environment::get().getManager(); + mShouldRender = mIsRunning; + if (!mIsRunning) + return; + if (mPredictedFrames != mPredictionFrame) + { + Log(Debug::Warning) << "advanceFrame() called out of order"; + return; + } + + xr->beginFrame(); + mPredictionFrame++; + mRenderFrame++; + mPredictedPoses[(int)PredictionSlice::Draw] = mPredictedPoses[(int)PredictionSlice::Predraw]; + } + void OpenXRSession::predictNext(int extraPeriods) { auto* xr = Environment::get().getManager(); auto* input = Environment::get().getInputManager(); auto mPredictedDisplayTime = xr->impl().frameState().predictedDisplayTime; - auto previousHeadPose = mPredictedPoses.head[(int)TrackedSpace::STAGE]; - - // Update pose predictions - mPredictedPoses.head[(int)TrackedSpace::STAGE] = xr->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::STAGE); - mPredictedPoses.head[(int)TrackedSpace::VIEW] = xr->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::VIEW); - mPredictedPoses.hands[(int)TrackedSpace::STAGE] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::STAGE); - mPredictedPoses.hands[(int)TrackedSpace::VIEW] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::VIEW); + PoseSets newPoses{}; + newPoses.head[(int)TrackedSpace::STAGE] = xr->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::STAGE); + newPoses.head[(int)TrackedSpace::VIEW] = xr->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::VIEW); + newPoses.hands[(int)TrackedSpace::STAGE] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::STAGE); + newPoses.hands[(int)TrackedSpace::VIEW] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::VIEW); auto stageViews = xr->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::STAGE); auto hmdViews = xr->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::VIEW); - mPredictedPoses.eye[(int)TrackedSpace::STAGE][(int)Side::LEFT_HAND] = fromXR(stageViews[(int)Side::LEFT_HAND].pose); - mPredictedPoses.eye[(int)TrackedSpace::VIEW][(int)Side::LEFT_HAND] = fromXR(hmdViews[(int)Side::LEFT_HAND].pose); - mPredictedPoses.eye[(int)TrackedSpace::STAGE][(int)Side::RIGHT_HAND] = fromXR(stageViews[(int)Side::RIGHT_HAND].pose); - mPredictedPoses.eye[(int)TrackedSpace::VIEW][(int)Side::RIGHT_HAND] = fromXR(hmdViews[(int)Side::RIGHT_HAND].pose); + newPoses.eye[(int)TrackedSpace::STAGE][(int)Side::LEFT_HAND] = fromXR(stageViews[(int)Side::LEFT_HAND].pose); + newPoses.eye[(int)TrackedSpace::VIEW][(int)Side::LEFT_HAND] = fromXR(hmdViews[(int)Side::LEFT_HAND].pose); + newPoses.eye[(int)TrackedSpace::STAGE][(int)Side::RIGHT_HAND] = fromXR(stageViews[(int)Side::RIGHT_HAND].pose); + newPoses.eye[(int)TrackedSpace::VIEW][(int)Side::RIGHT_HAND] = fromXR(hmdViews[(int)Side::RIGHT_HAND].pose); + mPredictedPoses[(int)PredictionSlice::Predraw] = newPoses; } } diff --git a/apps/openmw/mwvr/openxrsession.hpp b/apps/openmw/mwvr/openxrsession.hpp index 9bc333cd5..9bdcc68c0 100644 --- a/apps/openmw/mwvr/openxrsession.hpp +++ b/apps/openmw/mwvr/openxrsession.hpp @@ -19,11 +19,19 @@ extern void getEulerAngles(const osg::Quat& quat, float& yaw, float& pitch, floa class OpenXRSession { +public: using seconds = std::chrono::duration; using nanoseconds = std::chrono::nanoseconds; using clock = std::chrono::steady_clock; using time_point = clock::time_point; + enum class PredictionSlice + { + Predraw = 0, //!< Get poses predicted for the next frame to be drawn + Draw = 1, //!< Get poses predicted for the current rendering + NumSlices + }; + public: OpenXRSession(); ~OpenXRSession(); @@ -31,7 +39,7 @@ public: void setLayer(OpenXRLayerStack::Layer layerType, OpenXRLayer* layer); void swapBuffers(osg::GraphicsContext* gc); - const PoseSets& predictedPoses() const { return mPredictedPoses; }; + const PoseSets& predictedPoses(PredictionSlice slice); //! Call before updating poses and other inputs void waitFrame(); @@ -42,10 +50,22 @@ public: //! Yaw angle to be used for offsetting movement direction float movementYaw(void); + void advanceFrame(void); + + bool isRunning() { return mIsRunning; } + bool shouldRender() { return mShouldRender; } + OpenXRLayerStack mLayerStack{}; - PoseSets mPredictedPoses{}; - bool mPredictionsReady{ false }; + PoseSets mPredictedPoses[(int)PredictionSlice::NumSlices]{}; + + int mRenderFrame{ 0 }; + int mRenderedFrames{ 0 }; + int mPredictionFrame{ 1 }; + int mPredictedFrames{ 0 }; + + bool mIsRunning{ false }; + bool mShouldRender{ false }; }; } diff --git a/apps/openmw/mwvr/openxrswapchain.cpp b/apps/openmw/mwvr/openxrswapchain.cpp index 050d13fd8..b69a4538c 100644 --- a/apps/openmw/mwvr/openxrswapchain.cpp +++ b/apps/openmw/mwvr/openxrswapchain.cpp @@ -125,10 +125,6 @@ namespace MWVR { Timer timer("Swapchain::endFrame"); // Blit frame to swapchain - if (!mXR->sessionRunning()) - return -1; - - XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO }; uint32_t swapchainImageIndex = 0; CHECK_XRCMD(xrAcquireSwapchainImage(mSwapchain, &acquireInfo, &swapchainImageIndex)); diff --git a/apps/openmw/mwvr/openxrview.cpp b/apps/openmw/mwvr/openxrview.cpp index a358d589e..0f953321d 100644 --- a/apps/openmw/mwvr/openxrview.cpp +++ b/apps/openmw/mwvr/openxrview.cpp @@ -2,6 +2,11 @@ #include "openxrmanager.hpp" #include "openxrmanagerimpl.hpp" #include "../mwinput/inputmanagerimp.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwrender/renderingmanager.hpp" +#include "../mwrender/water.hpp" + #include #include @@ -46,6 +51,7 @@ namespace MWVR { camera->setGraphicsContext(gc); camera->setInitialDrawCallback(new OpenXRView::InitialDrawCallback()); + camera->setFinalDrawCallback(new OpenXRView::FinalDrawCallback()); return camera.release(); } @@ -57,13 +63,18 @@ namespace MWVR { mSwapchain->beginFrame(renderInfo.getState()->getGraphicsContext()); } mTimer.checkpoint("Prerender"); + } void OpenXRView::postrenderCallback(osg::RenderInfo& renderInfo) { + auto name = renderInfo.getCurrentCamera()->getName(); mTimer.checkpoint("Postrender"); - auto state = renderInfo.getState(); - auto gl = osg::GLExtensions::Get(state->getContextID(), false); + // Water RTT happens before the initial draw callback, + // so i have to disable it in postrender of the first eye, and then re-enable it + auto world = MWBase::Environment::get().getWorld(); + if (world) + world->toggleWaterRTT(name == "RightEye"); } void OpenXRView::swapBuffers(osg::GraphicsContext* gc) diff --git a/apps/openmw/mwvr/openxrview.hpp b/apps/openmw/mwvr/openxrview.hpp index 646ea7145..ea3d77387 100644 --- a/apps/openmw/mwvr/openxrview.hpp +++ b/apps/openmw/mwvr/openxrview.hpp @@ -19,6 +19,11 @@ namespace MWVR public: virtual void operator()(osg::RenderInfo& renderInfo) const; }; + class FinalDrawCallback : public osg::Camera::DrawCallback + { + public: + virtual void operator()(osg::RenderInfo& renderInfo) const; + }; protected: OpenXRView(osg::ref_ptr XR, std::string name, OpenXRSwapchain::Config config, osg::ref_ptr state); diff --git a/apps/openmw/mwvr/openxrviewer.cpp b/apps/openmw/mwvr/openxrviewer.cpp index 6d9bd3ce1..f17607b6a 100644 --- a/apps/openmw/mwvr/openxrviewer.cpp +++ b/apps/openmw/mwvr/openxrviewer.cpp @@ -31,16 +31,6 @@ namespace MWVR mCompositionLayerProjectionViews[0].pose.orientation.w = 1; mCompositionLayerProjectionViews[1].pose.orientation.w = 1; this->setName("OpenXRRoot"); - - this->setUserData(new TrackedNodeUpdateCallback(this)); - - mLeftHandTransform->setName("tracker l hand"); - mLeftHandTransform->setUpdateCallback(new TrackedNodeUpdateCallback(this)); - this->addChild(mLeftHandTransform); - - mRightHandTransform->setName("tracker r hand"); - mRightHandTransform->setUpdateCallback(new TrackedNodeUpdateCallback(this)); - this->addChild(mRightHandTransform); } OpenXRViewer::~OpenXRViewer(void) @@ -64,8 +54,6 @@ namespace MWVR void OpenXRViewer::traversals() { - auto* xr = Environment::get().getManager(); - xr->handleEvents(); mViewer->updateTraversal(); mViewer->renderingTraversals(); } @@ -256,8 +244,10 @@ namespace MWVR rightView->swapBuffers(gc); timer.checkpoint("Views"); - mCompositionLayerProjectionViews[0].pose = toXR(session->predictedPoses().eye[(int)TrackedSpace::STAGE][(int)Side::LEFT_HAND]); - mCompositionLayerProjectionViews[1].pose = toXR(session->predictedPoses().eye[(int)TrackedSpace::STAGE][(int)Side::RIGHT_HAND]); + auto& drawPoses = session->predictedPoses(OpenXRSession::PredictionSlice::Draw); + + mCompositionLayerProjectionViews[0].pose = toXR(drawPoses.eye[(int)TrackedSpace::STAGE][(int)Side::LEFT_HAND]); + mCompositionLayerProjectionViews[1].pose = toXR(drawPoses.eye[(int)TrackedSpace::STAGE][(int)Side::RIGHT_HAND]); timer.checkpoint("Poses"); @@ -304,13 +294,7 @@ namespace MWVR auto& view = mViews[name]; if (name == "LeftEye") - { - auto* xr = Environment::get().getManager(); - if (xr->sessionRunning()) - { - xr->beginFrame(); - } - } + Environment::get().getSession()->advanceFrame(); view->prerenderCallback(info); } @@ -330,86 +314,4 @@ namespace MWVR Log(Debug::Warning) << ("osg overwrote predraw"); } } - - void OpenXRViewer::updateTransformNode(osg::Object* object, osg::Object* data) - { - auto* hand_transform = dynamic_cast(object); - if (!hand_transform) - { - Log(Debug::Error) << "Update node was not PositionAttitudeTransform"; - return; - } - - auto* xr = Environment::get().getManager(); - auto* session = Environment::get().getSession(); - auto& poses = session->predictedPoses(); - auto handPosesStage = poses.hands[(int)TrackedSpace::STAGE]; - int side = (int)Side::LEFT_HAND; - if (hand_transform->getName() == "tracker r hand") - { - side = (int)Side::RIGHT_HAND; - } - - MWVR::Pose handStage = handPosesStage[side]; - MWVR::Pose headStage = poses.head[(int)TrackedSpace::STAGE]; - xr->playerScale(handStage); - xr->playerScale(headStage); - auto orientation = handStage.orientation; - auto position = handStage.position - headStage.position; - position = position * Environment::get().unitsPerMeter(); - - auto camera = mViewer->getCamera(); - auto viewMatrix = camera->getViewMatrix(); - - - // Align orientation with the game world - auto* inputManager = Environment::get().getInputManager(); - if (inputManager) - { - auto playerYaw = osg::Quat(-inputManager->mYaw, osg::Vec3d(0, 0, 1)); - position = playerYaw * position; - orientation = orientation * playerYaw; - } - - // Add camera offset - osg::Vec3 viewPosition; - osg::Vec3 center; - osg::Vec3 up; - - viewMatrix.getLookAt(viewPosition, center, up, 1.0); - position += viewPosition; - - //// Morrowind's meshes do not point forward by default. - //// Static since they do not need to be recomputed. - static float VRbias = osg::DegreesToRadians(-90.f); - static osg::Quat yaw(VRbias, osg::Vec3f(0, 0, 1)); - static osg::Quat pitch(2.f * VRbias, osg::Vec3f(0, 1, 0)); - static osg::Quat roll (2.f * VRbias, osg::Vec3f(1, 0, 0)); - - orientation = pitch * yaw * orientation; - - - if (hand_transform->getName() == "tracker r hand") - orientation = roll * orientation; - - // Hand are by default not well-centered - // These numbers are just a rough guess - osg::Vec3 offcenter = osg::Vec3(-0.175, 0., .033); - if (hand_transform->getName() == "tracker r hand") - offcenter.z() *= -1.; - osg::Vec3 recenter = orientation * offcenter; - position = position + recenter * Environment::get().unitsPerMeter(); - - hand_transform->setAttitude(orientation); - hand_transform->setPosition(position); - } - - bool - OpenXRViewer::TrackedNodeUpdateCallback::run( - osg::Object* object, - osg::Object* data) - { - mViewer->updateTransformNode(object, data); - return traverse(object, data); - } } diff --git a/apps/openmw/mwvr/openxrviewer.hpp b/apps/openmw/mwvr/openxrviewer.hpp index 21955d8f4..71be6d578 100644 --- a/apps/openmw/mwvr/openxrviewer.hpp +++ b/apps/openmw/mwvr/openxrviewer.hpp @@ -70,17 +70,6 @@ namespace MWVR OpenXRViewer* mViewer; }; - class TrackedNodeUpdateCallback : public osg::Callback - { - public: - TrackedNodeUpdateCallback(OpenXRViewer* viewer) : mViewer(viewer) {}; - - private: - virtual bool run(osg::Object* object, osg::Object* data); - - OpenXRViewer* mViewer; - }; - public: OpenXRViewer( osg::ref_ptr viewer); @@ -111,10 +100,6 @@ namespace MWVR osg::ref_ptr mViewer = nullptr; std::map > mViews{}; std::map > mCameras{}; - - SceneUtil::PositionAttitudeTransform* mLeftHandTransform = new SceneUtil::PositionAttitudeTransform(); - SceneUtil::PositionAttitudeTransform* mRightHandTransform = new SceneUtil::PositionAttitudeTransform(); - PredrawCallback* mPreDraw{ nullptr }; PostdrawCallback* mPostDraw{ nullptr }; diff --git a/apps/openmw/mwvr/openxrworldview.cpp b/apps/openmw/mwvr/openxrworldview.cpp index 414c86052..47cb4f4a3 100644 --- a/apps/openmw/mwvr/openxrworldview.cpp +++ b/apps/openmw/mwvr/openxrworldview.cpp @@ -143,6 +143,12 @@ namespace MWVR // Disable normal OSG FBO camera setup because it will undo the MSAA FBO configuration. renderer->setCameraRequiresSetUp(false); } + auto name = renderInfo.getCurrentCamera()->getName(); + } + + // TODO: This would have been useful but OSG never calls it. + void OpenXRWorldView::FinalDrawCallback::operator()(osg::RenderInfo& renderInfo) const + { } void @@ -151,37 +157,27 @@ namespace MWVR osg::View::Slave& slave) { mView->mTimer.checkpoint("UpdateSlave"); + auto* camera = slave._camera.get(); auto name = camera->getName(); - auto& poses = mSession->predictedPoses(); - - Pose viewPose{}; - Pose stagePose{}; + int side = 0; if (name == "LeftEye") { - mSession->waitFrame(); - - // Updating the head pose needs to happen after waitFrame(), - // But i can't call waitFrame from the input manager since it might - // not always be active. - auto* inputManager = Environment::get().getInputManager(); - if (inputManager) - inputManager->updateHead(); - - stagePose = poses.eye[(int)TrackedSpace::STAGE][(int)Side::LEFT_HAND]; - viewPose = poses.eye[(int)TrackedSpace::VIEW][(int)Side::LEFT_HAND]; - mView->setPredictedPose(viewPose); + side = (int)Side::LEFT_HAND; } else { - stagePose = poses.eye[(int)TrackedSpace::STAGE][(int)Side::RIGHT_HAND]; - viewPose = poses.eye[(int)TrackedSpace::VIEW][(int)Side::RIGHT_HAND]; - mView->setPredictedPose(viewPose); + side = (int)Side::RIGHT_HAND; } - if (!mXR->sessionRunning()) - return; + + auto& poses = mSession->predictedPoses(OpenXRSession::PredictionSlice::Predraw); + Pose viewPose{}; + Pose stagePose{}; + stagePose = poses.eye[(int)TrackedSpace::STAGE][side]; + viewPose = poses.eye[(int)TrackedSpace::VIEW][side]; + mView->setPredictedPose(viewPose); auto viewMatrix = view.getCamera()->getViewMatrix(); auto modifiedViewMatrix = viewMatrix * mView->viewMatrix(); diff --git a/apps/openmw/mwvr/realisticcombat.cpp b/apps/openmw/mwvr/realisticcombat.cpp index 8e6540235..dc82ecaf1 100644 --- a/apps/openmw/mwvr/realisticcombat.cpp +++ b/apps/openmw/mwvr/realisticcombat.cpp @@ -1,17 +1,41 @@ #include "realisticcombat.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwmechanics/weapontype.hpp" +#include namespace MWVR { namespace RealisticCombat { +static const char* stateToString(SwingState florida) +{ + switch (florida) + { + case SwingState_Idle: + return "Idle"; + case SwingState_Impact: + return "Impact"; + case SwingState_Ready: + return "Ready"; + case SwingState_Swinging: + return "Swinging"; + } +} + StateMachine::StateMachine(MWWorld::Ptr ptr) : ptr(ptr) {} +bool StateMachine::canSwing() +{ + 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) { - if (newState == state) - throw std::logic_error("Cannot transition to current state"); + Log(Debug::Verbose) << "Transition: " << stateToString(state) << " -> " << stateToString(newState); maxSwingVelocity = 0.f; timeSinceEnteredState = 0.f; @@ -28,11 +52,28 @@ void StateMachine::reset() 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; +} + void StateMachine::update(float dt, bool enabled) { auto* session = Environment::get().getSession(); - auto& handPose = session->predictedPoses().hands[(int)MWVR::TrackedSpace::STAGE][(int)MWVR::Side::RIGHT_HAND]; - auto& headPose = session->predictedPoses().head[(int)MWVR::TrackedSpace::STAGE]; + auto* world = MWBase::Environment::get().getWorld(); + auto& predictedPoses = session->predictedPoses(OpenXRSession::PredictionSlice::Predraw); + auto& handPose = predictedPoses.hands[(int)MWVR::TrackedSpace::STAGE][(int)MWVR::Side::RIGHT_HAND]; + auto& headPose = predictedPoses.head[(int)MWVR::TrackedSpace::STAGE]; + auto weaponType = world->getActiveWeaponType(); + + enabled = enabled && isMeleeWeapon(weaponType); if (mEnabled != enabled) { @@ -47,17 +88,22 @@ void StateMachine::update(float dt, bool enabled) // First determine direction of different swing types - // Thrust is radially out from the head. Which is the same as the position of the hand relative to the head, ignoring height component - osg::Vec3 thrustDirection = handPose.position - headPose.position; - thrustDirection.z() = 0; - thrustDirection.normalize(); + // Discover orientation of weapon + osg::Quat weaponDir = handPose.orientation; - // Chop is straight down - osg::Vec3 chopDirection = osg::Vec3(0.f, 0.f, -1.f); + // 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; - // Swing is normal to the plane created by Chop x Thrust - osg::Vec3 slashDirection = chopDirection ^ thrustDirection; - slashDirection.normalize(); + // Thrust means stabbing in the direction of the weapon + osg::Vec3 thrustDirection = weaponDir * osg::Vec3{ 0,1,0 }; + + // Chop is vertical, relative to the orientation of the weapon + osg::Vec3 chopDirection = weaponDir * osg::Vec3{ 0,0,1 }; + + // Swing is horizontal, relative to the orientation of the weapon + osg::Vec3 slashDirection = weaponDir * osg::Vec3{ 1,0,0 }; // Next determine current hand movement @@ -72,37 +118,36 @@ void StateMachine::update(float dt, bool enabled) osg::Vec3 movement = handPose.position - previousPosition; movementSinceEnteredState += movement.length(); previousPosition = handPose.position; - osg::Vec3 swingDirection = movement / dt; + osg::Vec3 swingVector = movement / dt; // Compute swing velocity // Unidirectional - thrustVelocity = swingDirection * thrustDirection; + thrustVelocity = swingVector * thrustDirection; // Bidirectional - slashVelocity = std::abs(swingDirection * slashDirection); - chopVelocity = std::abs(swingDirection * chopDirection); + slashVelocity = std::abs(swingVector * slashDirection); + chopVelocity = std::abs(swingVector * chopDirection); + velocity = swingVector.length(); // Pick swing type based on greatest current velocity // Note i use abs() of thrust velocity to prevent accidentally triggering // chop or slash when player is withdrawing his limb. if (std::abs(thrustVelocity) > slashVelocity && std::abs(thrustVelocity) > chopVelocity) { - velocity = thrustVelocity; swingType = ESM::Weapon::AT_Thrust; } else if (slashVelocity > chopVelocity) { - velocity = slashVelocity; swingType = ESM::Weapon::AT_Slash; } else { - velocity = chopVelocity; swingType = ESM::Weapon::AT_Chop; } - switch (state) { + case SwingState_Idle: + return update_idleState(); case SwingState_Ready: return update_readyState(); case SwingState_Swinging: @@ -112,11 +157,21 @@ void StateMachine::update(float dt, bool enabled) } } +void StateMachine::update_idleState() +{ + if (timeSinceEnteredState >= minimumPeriod) + transition_idleToReady(); +} + +void StateMachine::transition_idleToReady() +{ + transition(SwingState_Ready); +} + void StateMachine::update_readyState() { - if (velocity >= minVelocity) - if(timeSinceEnteredState >= minimumPeriod) - return transition_readyToSwinging(); + if (canSwing()) + return transition_readyToSwinging(); } void StateMachine::transition_readyToSwinging() @@ -124,8 +179,9 @@ void StateMachine::transition_readyToSwinging() shouldSwish = true; transition(SwingState_Swinging); - // As an exception, update the new state immediately to allow - // same-frame impacts. + // As a special case, update the new state immediately to allow + // same-frame impacts. This is important if the player is moving + // at a velocity close to the minimum velocity. update_swingingState(); } @@ -151,47 +207,47 @@ void StateMachine::update_swingingState() maxSwingVelocity = std::max(velocity, maxSwingVelocity); strength = std::min(1.f, (maxSwingVelocity - minVelocity) / maxVelocity); - if (velocity < minVelocity) - return transition_swingingToReady(); - - // Require a minimum period of swinging before a hit can be made - // This is to prevent annoying little microswings + // Require a minimum movement of the hand before a hit can be made + // This is to prevent microswings if (movementSinceEnteredState > minimumPeriod) { playSwish(); - - // Note: calling hit with simulated=true to avoid side effects - if (ptr.getClass().hit(ptr, strength, swingType, true)) + if (// When velocity falls below minimum, register the miss + !canSwing() + // Call hit with simulated=true to check for hit + || ptr.getClass().hit(ptr, strength, swingType, true) + ) return transition_swingingToImpact(); } + + // If velocity drops below minimum before minimum movement was achieved, + // drop back to ready + if (!canSwing()) + return transition_swingingToReady(); } void StateMachine::transition_swingingToReady() { - if (movementSinceEnteredState > minimumPeriod) - { - playSwish(); - ptr.getClass().hit(ptr, strength, swingType, false); - } transition(SwingState_Ready); } void StateMachine::transition_swingingToImpact() { - playSwish(); ptr.getClass().hit(ptr, strength, swingType, false); transition(SwingState_Impact); + std::cout << "Transition: Swing -> Impact" << std::endl; } void StateMachine::update_impactState() { if (velocity < minVelocity) - return transition_impactToReady(); + return transition_impactToIdle(); } -void StateMachine::transition_impactToReady() +void StateMachine::transition_impactToIdle() { - transition(SwingState_Ready); + transition(SwingState_Idle); + std::cout << "Transition: Impact -> Ready" << std::endl; } }} diff --git a/apps/openmw/mwvr/realisticcombat.hpp b/apps/openmw/mwvr/realisticcombat.hpp index bbc567cc2..bbcf284b2 100644 --- a/apps/openmw/mwvr/realisticcombat.hpp +++ b/apps/openmw/mwvr/realisticcombat.hpp @@ -13,9 +13,10 @@ namespace MWVR { namespace RealisticCombat { enum SwingState { - SwingState_Ready = 0, - SwingState_Swinging = 1, - SwingState_Impact = 2 + SwingState_Idle = 0, + SwingState_Ready = 1, + SwingState_Swinging = 2, + SwingState_Impact = 3 }; struct StateMachine @@ -27,7 +28,7 @@ struct StateMachine float velocity = 0.f; float maxSwingVelocity = 0.f; - SwingState state = SwingState_Ready; + SwingState state = SwingState_Idle; MWWorld::Ptr ptr = MWWorld::Ptr(); int swingType = -1; float strength = 0.f; @@ -36,7 +37,7 @@ struct StateMachine float slashVelocity{ 0.f }; float chopVelocity{ 0.f }; - float minimumPeriod{ .5f }; + float minimumPeriod{ .25f }; float timeSinceEnteredState = { 0.f }; float movementSinceEnteredState = { 0.f }; @@ -47,6 +48,8 @@ struct StateMachine StateMachine(MWWorld::Ptr ptr); + bool canSwing(); + void playSwish(); void reset(); @@ -54,6 +57,9 @@ struct StateMachine void update(float dt, bool enabled); + void update_idleState(); + void transition_idleToReady(); + void update_readyState(); void transition_readyToSwinging(); @@ -62,7 +68,7 @@ struct StateMachine void transition_swingingToImpact(); void update_impactState(); - void transition_impactToReady(); + void transition_impactToIdle(); }; }} diff --git a/apps/openmw/mwvr/vranimation.cpp b/apps/openmw/mwvr/vranimation.cpp index ce19634ea..340be4c1c 100644 --- a/apps/openmw/mwvr/vranimation.cpp +++ b/apps/openmw/mwvr/vranimation.cpp @@ -66,36 +66,33 @@ namespace MWVR { -// This will work for a prototype. But finger/arm control might be better implemented using the -// existing animation system, implementing this as an animation source. -// But I'm not sure it would be since these are not classical animations. -// It would make it easier to control priority, and later allow for users to add their own stuff to animations based on VR/touch input. -// But openmw doesn't really have any concepts for user animation overrides as far as i can tell. +// Some weapon types, such as spellcast, are classified as melee even though they are not. +// All the fake melee types have negative type enum, but also so does hand to hand. +// I think this covers all the cases +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; +} -/// Implements dummy control of the forearm, to control mesh/bone deformation of the hand. +/// Implements VR control of the forearm, to control mesh/bone deformation of the hand. class ForearmController : public osg::NodeCallback { public: - ForearmController(osg::Node* relativeTo, SceneUtil::PositionAttitudeTransform* tracker); + ForearmController() = default; void setEnabled(bool enabled) { mEnabled = enabled; }; void operator()(osg::Node* node, osg::NodeVisitor* nv); private: - bool mEnabled = true; - osg::Quat mRotate{}; - osg::Node* mRelativeTo; - osg::Matrix mOffset{ osg::Matrix::identity() }; - bool mOffsetInitialized = false; - SceneUtil::PositionAttitudeTransform* mTracker; + bool mEnabled{ true }; }; -ForearmController::ForearmController(osg::Node* relativeTo, SceneUtil::PositionAttitudeTransform* tracker) - : mRelativeTo(relativeTo) - , mTracker(tracker) -{ -} - void ForearmController::operator()(osg::Node* node, osg::NodeVisitor* nv) { if (!mEnabled) @@ -105,44 +102,102 @@ void ForearmController::operator()(osg::Node* node, osg::NodeVisitor* nv) } osg::MatrixTransform* transform = static_cast(node); - if (!mOffsetInitialized) + + auto* xr = Environment::get().getManager(); + auto* session = Environment::get().getSession(); + auto* xrViewer = Environment::get().getViewer(); + auto& poses = session->predictedPoses(OpenXRSession::PredictionSlice::Predraw); + auto handPosesStage = poses.hands[(int)TrackedSpace::STAGE]; + int side = (int)Side::RIGHT_HAND; + if (node->getName().find_first_of("L") != std::string::npos) { - // This is a bit of a hack. - // Trackers track hands, not forearms. - // But i have to transform the forearms to account for deformations, - // so i subtract the hand transform from the final transform to center the hands. - std::string handName = node->getName() == "Bip01 L Forearm" ? "Bip01 L Hand" : "Bip01 R Hand"; - SceneUtil::FindByNameVisitor findHandVisitor(handName); - node->accept(findHandVisitor); - mOffset = findHandVisitor.mFoundNode->asTransform()->asMatrixTransform()->getInverseMatrix(); - mOffsetInitialized = true; + side = (int)Side::LEFT_HAND; + // We base ourselves on the world position of the camera + // Ensure it is updated before placing the hands + // I'm sure this can be achieved properly by managing the scene graph better. + MWBase::Environment::get().getWorld()->getRenderingManager().getCamera()->updateCamera(); } + + MWVR::Pose handStage = handPosesStage[side]; + MWVR::Pose headStage = poses.head[(int)TrackedSpace::STAGE]; + xr->playerScale(handStage); + xr->playerScale(headStage); + + auto orientation = handStage.orientation; + + auto position = handStage.position - headStage.position; + position = position * Environment::get().unitsPerMeter(); + + auto camera = xrViewer->mViewer->getCamera(); + auto viewMatrix = camera->getViewMatrix(); + + + // Align orientation with the game world + auto* inputManager = Environment::get().getInputManager(); + if (inputManager) + { + auto playerYaw = osg::Quat(-inputManager->mYaw, osg::Vec3d(0, 0, 1)); + position = playerYaw * position; + orientation = orientation * playerYaw; + } + + // Add camera offset + osg::Vec3 viewPosition; + osg::Vec3 center; + osg::Vec3 up; + + viewMatrix.getLookAt(viewPosition, center, up, 1.0); + position += viewPosition; + + //// Morrowind's meshes do not point forward by default. + //// Declare the offsets static since they do not need to be recomputed. + static float VRbias = osg::DegreesToRadians(-90.f); + static osg::Quat yaw(-VRbias, osg::Vec3f(0, 0, 1)); + static osg::Quat pitch(2.f * VRbias, osg::Vec3f(0, 1, 0)); + static osg::Quat roll(2 * VRbias, osg::Vec3f(1, 0, 0)); + orientation = yaw * orientation; + if (side == (int)Side::LEFT_HAND) + orientation = roll * orientation; + + // Undo the wrist translate + auto* hand = transform->getChild(0); + auto handMatrix = hand->asTransform()->asMatrixTransform()->getMatrix(); + position -= orientation * handMatrix.getTrans(); + + // Center hand mesh on tracking + // This is just an estimate from trial and error, any suggestion for improving this is welcome + position -= orientation * osg::Vec3{ 15,0,0 }; + // Get current world transform of limb osg::Matrix worldToLimb = osg::computeLocalToWorld(node->getParentalNodePaths()[0]); // Get current world of the reference node osg::Matrix worldReference = osg::Matrix::identity(); - // New transform is reference node + tracker. - mTracker->computeLocalToWorldMatrix(worldReference, nullptr); - // Get hand - transform->setMatrix(mOffset * worldReference * osg::Matrix::inverse(worldToLimb) * transform->getMatrix()); + // New transform based on tracking. + worldReference.preMultTranslate(position); + worldReference.preMultRotate(orientation); + // Finally, set transform + transform->setMatrix(worldReference * osg::Matrix::inverse(worldToLimb) * transform->getMatrix()); - // TODO: Continued traversal is necessary to allow update of new hand poses such as gripping a weapon. - // But I want to disable idle animations. + Log(Debug::Verbose) << "Updating hand: " << node->getName(); + + // Omit nested callbacks to override animations of this node + osg::ref_ptr ncb = getNestedCallback(); + setNestedCallback(nullptr); traverse(node, nv); + setNestedCallback(ncb); } /// Implements control of a finger by overriding rotation class FingerController : public osg::NodeCallback { public: - FingerController(osg::Quat rotate) : mRotate(rotate) {}; + FingerController() {}; void setEnabled(bool enabled) { mEnabled = enabled; }; void operator()(osg::Node* node, osg::NodeVisitor* nv); private: bool mEnabled = true; - osg::Quat mRotate{}; }; void FingerController::operator()(osg::Node* node, osg::NodeVisitor* nv) @@ -153,36 +208,224 @@ void FingerController::operator()(osg::Node* node, osg::NodeVisitor* nv) return; } - // This update needs to hard override all other animation updates. - // To do this i need to make sure no further update calls are made. - // Therefore i do not traverse normally but instead explicitly fetch - // the children i want to update and update them here. - // I'm sure this could be done in a cleaner way + osg::Quat rotate{ 0,0,0,1 }; + // TODO: + // To make finger pointing more natural each joint should angle down roughly 5-6 degrees per joint. + // But this creates obvious visual conflicts with the hand and the rest of the fingers which are not posed naturally, + // and looks particularly riddiculous when a weapon is drawn. + // Therefore, for the time being i will simply have them point straight forward relative to the current hand rotation. + // This leads to particularly awkward finger pointing while a weapon is drawn and should be replaced by a + // complete override of all hand animations by the end of 2090. + + //////// Add a slight rotation down of the fingers to naturalize the pointing. + //////rotate = osg::Quat(osg::PI_4 / 8, osg::Vec3{ 0,1,0 }) * rotate; + //////if (node->getName() == "Bip01 R Finger1") + //////{ + ////// auto* world = MWBase::Environment::get().getWorld(); + + ////// // 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. + ////// // Fingers need to angle back up to keep pointing natural. + ////// if (world->getActiveWeaponType() >= 0) + ////// { + ////// rotate = osg::Quat(-osg::PI_4, osg::Vec3{ 0,1,0 }) * rotate; + ////// } + //////} // First, update the base of the finger to the overriding orientation auto matrixTransform = node->asTransform()->asMatrixTransform(); auto matrix = matrixTransform->getMatrix(); - matrix.setRotate(mRotate); + matrix.setRotate(rotate); matrixTransform->setMatrix(matrix); - // Next update the tip. - // Note that for now both tips are just given osg::Quat(0,0,0,1) as that amounts to pointing forward. - auto tip = matrixTransform->getChild(0)->asTransform()->asMatrixTransform(); - matrix = tip->getMatrix(); - matrix.setRotate(mRotate); - tip->setMatrix(matrix); - // Finally, if pointing forward is enabled we need to intersect the scene to find where the player is pointing - // So that we can display a beam to visualize where the player is pointing. + // Omit nested callbacks to override animations of this node + osg::ref_ptr ncb = getNestedCallback(); + setNestedCallback(nullptr); + traverse(node, nv); + setNestedCallback(ncb); - // Dig up the pointer transform + // Update where the player is currently pointing auto* anim = MWVR::Environment::get().getPlayerAnimation(); - if (anim) + if (anim && node->getName() == "Bip01 R Finger1") + { anim->updatePointerTarget(); + } } +/// Implements control of a finger by overriding rotation +class HandController : public osg::NodeCallback +{ +public: + HandController() = default; + void setEnabled(bool enabled) { mEnabled = enabled; }; + void operator()(osg::Node* node, osg::NodeVisitor* nv); + +private: + bool mEnabled = true; +}; + +void HandController::operator()(osg::Node* node, osg::NodeVisitor* nv) +{ + if (!mEnabled) + { + traverse(node, nv); + return; + } + float PI_2 = osg::PI_2; + if (node->getName() == "Bip01 L Hand") + PI_2 = -PI_2; + float PI_4 = PI_2 / 2.f; + + osg::Quat rotate{ 0,0,0,1 }; + auto* world = MWBase::Environment::get().getWorld(); + auto windowManager = MWBase::Environment::get().getWindowManager(); + auto weaponType = world->getActiveWeaponType(); + // Morrowind models do not hold most weapons at a natural angle, so i rotate the hand + // to more natural angles on weapons to allow more comfortable combat. + if (!windowManager->isGuiMode()) + { + + switch (weaponType) + { + case ESM::Weapon::None: + case ESM::Weapon::HandToHand: + case ESM::Weapon::MarksmanThrown: + case ESM::Weapon::Spell: + case ESM::Weapon::Arrow: + case ESM::Weapon::Bolt: + // No adjustment + break; + case ESM::Weapon::MarksmanCrossbow: + // Crossbow points upwards. Assumedly because i am overriding hand animations. + rotate = osg::Quat(PI_4 / 1.05, osg::Vec3{ 0,1,0 }) * osg::Quat(0.06, osg::Vec3{ 0,0,1 }); + break; + case ESM::Weapon::MarksmanBow: + // Bow points down by default, rotate it back up a little + rotate = osg::Quat(-PI_2 * .10f, osg::Vec3{ 0,1,0 }); + break; + default: + // Melee weapons Need adjustment + rotate = osg::Quat(PI_4, osg::Vec3{ 0,1,0 }); + break; + } + } + + auto matrixTransform = node->asTransform()->asMatrixTransform(); + auto matrix = matrixTransform->getMatrix(); + matrix.setRotate(rotate); + matrixTransform->setMatrix(matrix); + + + // Omit nested callbacks to override animations of this node + osg::ref_ptr ncb = getNestedCallback(); + setNestedCallback(nullptr); + traverse(node, nv); + setNestedCallback(ncb); +} + +/// Implements control of weapon direction +class WeaponDirectionController : public osg::NodeCallback +{ +public: + WeaponDirectionController() = default; + void setEnabled(bool enabled) { mEnabled = enabled; }; + void operator()(osg::Node* node, osg::NodeVisitor* nv); + +private: + bool mEnabled = true; +}; + +void WeaponDirectionController::operator()(osg::Node* node, osg::NodeVisitor* nv) +{ + if (!mEnabled) + { + traverse(node, nv); + return; + } + + // Arriving here implies a parent, no need to check + auto parent = static_cast(node->getParent(0)); + + + + osg::Quat rotate{ 0,0,0,1 }; + auto* world = MWBase::Environment::get().getWorld(); + auto weaponType = world->getActiveWeaponType(); + switch (weaponType) + { + case ESM::Weapon::MarksmanThrown: + case ESM::Weapon::Spell: + case ESM::Weapon::Arrow: + case ESM::Weapon::Bolt: + case ESM::Weapon::HandToHand: + case ESM::Weapon::MarksmanBow: + case ESM::Weapon::MarksmanCrossbow: + // Rotate to point straight forward, reverting any rotation of the hand to keep aim consistent. + rotate = parent->getInverseMatrix().getRotate(); + rotate = osg::Quat(-osg::PI_2, osg::Vec3{ 0,0,1 }) * rotate; + break; + default: + // Melee weapons point straight up from the hand + rotate = osg::Quat(-osg::PI_2, osg::Vec3{ 0,1,0 }); + break; + } + + auto matrixTransform = node->asTransform()->asMatrixTransform(); + auto matrix = matrixTransform->getMatrix(); + matrix.setRotate(rotate); + matrixTransform->setMatrix(matrix); + + traverse(node, nv); +} + +/// Implements control of the weapon pointer +class WeaponPointerController : public osg::NodeCallback +{ +public: + WeaponPointerController() = default; + void setEnabled(bool enabled) { mEnabled = enabled; }; + void operator()(osg::Node* node, osg::NodeVisitor* nv); + +private: + bool mEnabled = true; +}; + +void WeaponPointerController::operator()(osg::Node* node, osg::NodeVisitor* nv) +{ + if (!mEnabled) + { + traverse(node, nv); + return; + } + + auto matrixTransform = node->asTransform()->asMatrixTransform(); + auto world = MWBase::Environment::get().getWorld(); + auto weaponType = world->getActiveWeaponType(); + auto windowManager = MWBase::Environment::get().getWindowManager(); + + if (!isMeleeWeapon(weaponType) && !windowManager->isGuiMode()) + { + // Ranged weapons should show a pointer to where they are targeting + matrixTransform->setMatrix( + osg::Matrix::scale(1.f, 64.f, 1.f) + ); + } + else + { + // Hide the pointer + matrixTransform->setMatrix( + osg::Matrix::scale(1.f, 64.f, 1.f) + //osg::Matrix::scale(0.f, 0.f, 0.f) + ); + } + + // First, update the base of the finger to the overriding orientation + + traverse(node, nv); +} VRAnimation::VRAnimation( const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, @@ -196,10 +439,35 @@ VRAnimation::VRAnimation( // Pushing the camera forward instead would produce an unnatural extra movement when rotating the player model. , mModelOffset(new osg::MatrixTransform(osg::Matrix::translate(osg::Vec3(0,-15,0)))) { - mIndexFingerControllers[0] = osg::ref_ptr (new FingerController(osg::Quat(0, 0, 0, 1))); - mIndexFingerControllers[1] = osg::ref_ptr (new FingerController(osg::Quat(0, 0, 0, 1))); + for (int i = 0; i < 2; i++) + { + mIndexFingerControllers[i] = new FingerController; + mForearmControllers[i] = new ForearmController; + mHandControllers[i] = new HandController; + } + + mWeaponDirectionTransform = new osg::MatrixTransform(); + mWeaponDirectionTransform->setName("Weapon Direction"); + mWeaponDirectionTransform->setUpdateCallback(new WeaponDirectionController); + mModelOffset->setName("ModelOffset"); - createPointer(); + mPointerGeometry = createPointerGeometry(); + mPointerRescale = new osg::MatrixTransform(); + mPointerRescale->addChild(mPointerGeometry); + mPointerTransform = new osg::MatrixTransform(); + mPointerTransform->addChild(mPointerRescale); + mPointerTransform->setName("Pointer Transform"); + // Morrowind's hands don't actually point forward, so we have to reorient the pointer. + mPointerTransform->setMatrix(osg::Matrix::rotate(osg::Quat(-osg::PI_2, osg::Vec3f(0, 0, 1)))); + + mWeaponPointerTransform = new osg::MatrixTransform(); + mWeaponPointerTransform->addChild(mPointerGeometry); + mWeaponPointerTransform->setMatrix( + osg::Matrix::scale(0.f, 0.f, 0.f) + ); + mWeaponPointerTransform->setName("Weapon Pointer"); + mWeaponPointerTransform->setUpdateCallback(new WeaponPointerController); + mWeaponDirectionTransform->addChild(mWeaponPointerTransform); } VRAnimation::~VRAnimation() {}; @@ -236,58 +504,29 @@ void VRAnimation::updateParts() removeIndividualPart(ESM::PartReferenceType::PRT_LAnkle); removeIndividualPart(ESM::PartReferenceType::PRT_RAnkle); } + void VRAnimation::setPointForward(bool enabled) { - auto found00 = mNodeMap.find("bip01 r finger1"); - //auto weapon = mNodeMap.find("weapon"); - if (found00 != mNodeMap.end()) + auto finger = mNodeMap.find("bip01 r finger1"); + if (finger != mNodeMap.end()) { - auto base_joint = found00->second; + auto base_joint = finger->second; auto second_joint = base_joint->getChild(0)->asTransform()->asMatrixTransform(); assert(second_joint); - second_joint->removeChild(mPointerTransform); - //weapon->second->removeChild(mWeaponDirectionTransform); - mWeaponDirectionTransform->removeChild(mPointerGeometry); base_joint->removeUpdateCallback(mIndexFingerControllers[0]); + second_joint->removeUpdateCallback(mIndexFingerControllers[1]); if (enabled) { - second_joint->addChild(mPointerTransform); - //weapon->second->addChild(mWeaponDirectionTransform); - mWeaponDirectionTransform->addChild(mPointerGeometry); base_joint->addUpdateCallback(mIndexFingerControllers[0]); + second_joint->addUpdateCallback(mIndexFingerControllers[1]); + } } -} - -void VRAnimation::createPointer(void) -{ - mPointerGeometry = createPointerGeometry(); - mPointerTransform = new osg::MatrixTransform(); - mPointerTransform->addChild(mPointerGeometry); - mPointerTransform->setName("Pointer Transform"); - - mWeaponPointerTransform = new osg::MatrixTransform(); - mWeaponPointerTransform->addChild(mPointerGeometry); - mWeaponPointerTransform->setMatrix( - osg::Matrix::scale(64.f, 1.f, 1.f) - ); - mWeaponPointerTransform->setName("Weapon Pointer"); - - mWeaponDirectionTransform = new osg::MatrixTransform(); - mWeaponDirectionTransform->addChild(mWeaponPointerTransform); - mWeaponDirectionTransform->setMatrix( - osg::Matrix::rotate(osg::DegreesToRadians(-90.f), osg::Y_AXIS) - ); - mWeaponDirectionTransform->setName("Weapon Direction"); - - mWeaponAdjustment = new osg::MatrixTransform(); - //mWeaponAdjustment->addChild(mWeaponPointerTransform); - mWeaponAdjustment->setMatrix( - osg::Matrix::rotate(osg::DegreesToRadians(90.f), osg::Y_AXIS) - ); - mWeaponAdjustment->setName("Weapon Adjustment"); + mPointerTransform->removeChild(mPointerRescale); + if (enabled) + mPointerTransform->addChild(mPointerRescale); } osg::ref_ptr VRAnimation::createPointerGeometry(void) @@ -300,8 +539,8 @@ osg::ref_ptr VRAnimation::createPointerGeometry(void) osg::Vec3 vertices[]{ {0, 0, 0}, // origin {1, 1, -1}, // top_left - {1, -1, -1}, // bottom_left - {1, -1, 1}, // bottom_right + {-1, 1, -1}, // bottom_left + {-1, 1, 1}, // bottom_right {1, 1, 1}, // top_right }; @@ -340,8 +579,6 @@ osg::ref_ptr VRAnimation::createPointerGeometry(void) osg::ref_ptr normals = new osg::Vec3Array; normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f)); - - geometry->setVertexArray(vertexArray); geometry->setColorArray(colorArray, osg::Array::BIND_PER_VERTEX); geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, numVertices)); @@ -367,58 +604,46 @@ void VRAnimation::addControllers() { NpcAnimation::addControllers(); - // TODO: - // Those controllers should be made using the openxr session *here* rather than magicking up - // a couple nodes by searching the scene graph. - - //mNodeMap, mActiveControllers, mObjectRoot.get() - SceneUtil::FindByNameVisitor findXRVisitor("OpenXRRoot", osg::NodeVisitor::TRAVERSE_PARENTS); - getObjectRoot()->accept(findXRVisitor); - auto* xrRoot = findXRVisitor.mFoundNode; - if (!xrRoot) - { - throw std::logic_error("Viewmode is VM_VRHeadless but OpenXRRoot does not exist"); - } - for (int i = 0; i < 2; ++i) { - mHandControllers[i] = nullptr; - - SceneUtil::FindByNameVisitor findTrackerVisitor(i == 0 ? "tracker l hand" : "tracker r hand"); - xrRoot->accept(findTrackerVisitor); - if (!findTrackerVisitor.mFoundNode) - continue; - - SceneUtil::PositionAttitudeTransform* tracker = dynamic_cast(findTrackerVisitor.mFoundNode); - - auto found = mNodeMap.find(i == 0 ? "bip01 l forearm" : "bip01 r forearm"); - if (found != mNodeMap.end()) + auto forearm = mNodeMap.find(i == 0 ? "bip01 l forearm" : "bip01 r forearm"); + if (forearm != mNodeMap.end()) { - osg::Node* node = found->second; - mForearmControllers[i] = new ForearmController(mObjectRoot, tracker); + auto node = forearm->second; + node->removeUpdateCallback(mForearmControllers[i]); node->addUpdateCallback(mForearmControllers[i]); - mActiveControllers.insert(std::make_pair(node, mForearmControllers[i])); + } + + auto hand = mNodeMap.find(i == 0 ? "bip01 l hand" : "bip01 r hand"); + if (hand != mNodeMap.end()) + { + auto node = hand->second; + node->removeUpdateCallback(mHandControllers[i]); + node->addUpdateCallback(mHandControllers[i]); } } + auto hand = mNodeMap.find("bip01 r hand"); + if (hand != mNodeMap.end()) + { + hand->second->removeChild(mWeaponDirectionTransform); + hand->second->addChild(mWeaponDirectionTransform); + } + auto finger = mNodeMap.find("bip01 r finger11"); + if (finger != mNodeMap.end()) + { + finger->second->removeChild(mPointerTransform); + finger->second->addChild(mPointerTransform); + } auto parent = mObjectRoot->getParent(0); - if (parent->getName() == "Player Root") { auto group = parent->asGroup(); group->removeChildren(0, parent->getNumChildren()); group->addChild(mModelOffset); mModelOffset->addChild(mObjectRoot); - - auto weapon = mNodeMap.find("weapon"); - weapon->second->addChild(mWeaponDirectionTransform); } - - - - - } void VRAnimation::enableHeadAnimation(bool) { @@ -454,13 +679,13 @@ void VRAnimation::updatePointerTarget() auto* world = MWBase::Environment::get().getWorld(); if (world) { - mPointerTransform->setMatrix(osg::Matrix::scale(1, 1, 1)); + mPointerRescale->setMatrix(osg::Matrix::scale(1, 1, 1)); mDistanceToPointerTarget = world->getTargetObject(mPointerTarget, mPointerTransform); if(mDistanceToPointerTarget >= 0) - mPointerTransform->setMatrix(osg::Matrix::scale(mDistanceToPointerTarget, 0.25f, 0.25f)); + mPointerRescale->setMatrix(osg::Matrix::scale(0.25f, mDistanceToPointerTarget, 0.25f)); else - mPointerTransform->setMatrix(osg::Matrix::scale(10000.f, 0.25f, 0.25f)); + mPointerRescale->setMatrix(osg::Matrix::scale(0.25f, 10000.f, 0.25f)); } } diff --git a/apps/openmw/mwvr/vranimation.hpp b/apps/openmw/mwvr/vranimation.hpp index 71230873e..5c353dda1 100644 --- a/apps/openmw/mwvr/vranimation.hpp +++ b/apps/openmw/mwvr/vranimation.hpp @@ -65,18 +65,17 @@ public: void updatePointerTarget(); public: - void createPointer(void); static osg::ref_ptr createPointerGeometry(void); public: std::shared_ptr mSession; - ForearmController* mForearmControllers[2]{}; - HandController* mHandControllers[2]{}; + osg::ref_ptr mForearmControllers[2]; + osg::ref_ptr mHandControllers[2]; osg::ref_ptr mIndexFingerControllers[2]; osg::ref_ptr mModelOffset; osg::ref_ptr mPointerGeometry{ nullptr }; + osg::ref_ptr mPointerRescale{ nullptr }; osg::ref_ptr mPointerTransform{ nullptr }; - osg::ref_ptr mWeaponAdjustment{ nullptr }; osg::ref_ptr mWeaponDirectionTransform{ nullptr }; osg::ref_ptr mWeaponPointerTransform{ nullptr }; MWRender::RayResult mPointerTarget{}; diff --git a/apps/openmw/mwvr/vrenvironment.cpp b/apps/openmw/mwvr/vrenvironment.cpp index 88b982e27..07799b77a 100644 --- a/apps/openmw/mwvr/vrenvironment.cpp +++ b/apps/openmw/mwvr/vrenvironment.cpp @@ -32,9 +32,6 @@ void MWVR::Environment::cleanup() if (mMenuManager) delete mMenuManager; mMenuManager = nullptr; - if (mPlayerAnimation) - delete mPlayerAnimation; - mPlayerAnimation = nullptr; if (mViewer) delete mViewer; mViewer = nullptr; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 6004a34a6..49456ab24 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -43,6 +43,11 @@ #include "../mwphysics/physicssystem.hpp" +#ifdef USE_OPENXR +#include "../mwvr/vrenvironment.hpp" +#include "../mwvr/vranimation.hpp" +#endif + namespace { ESM::EffectList getMagicBoltData(std::vector& projectileIDs, std::set& sounds, float& speed, std::string& texture, std::string& sourceName, const std::string& id) @@ -269,6 +274,16 @@ namespace MWWorld return; osg::Quat orient; +#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]); + orient = worldMatrix.getRotate(); + pos = worldMatrix.getTrans(); + } + else +#endif if (caster.getClass().isActor()) orient = osg::Quat(caster.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(caster.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 385c5a25b..1e42edf34 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1154,7 +1154,7 @@ namespace MWWorld if (ptr == getPlayerPtr()) { #ifdef USE_OPENXR - // TODO: Configurable realistic fighting. + // TODO: Configurable realistic fighting ? // Use current aim of weapon to impact // TODO: Use bounding box of weapon instead ? @@ -3063,8 +3063,11 @@ namespace MWWorld // for player we can take faced object first MWWorld::Ptr target; +#ifndef USE_OPENXR + // Does not apply to VR if (actor == MWMechanics::getPlayer()) target = getFacedObject(); +#endif // if the faced object can not be activated, do not use it if (!target.isEmpty() && !target.getClass().hasToolTip(target)) @@ -3101,10 +3104,21 @@ namespace MWWorld osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); +#ifdef USE_OPENXR + if (actor == MWMechanics::getPlayer()) + { + auto* anim = MWVR::Environment::get().getPlayerAnimation(); + osg::Matrix worldMatrix = osg::computeLocalToWorld(anim->mWeaponDirectionTransform->getParentalNodePaths()[0]); + origin = worldMatrix.getTrans(); + orient = worldMatrix.getRotate(); + } +#endif + osg::Vec3f direction = orient * osg::Vec3f(0,1,0); float distance = getMaxActivationDistance(); osg::Vec3f dest = origin + direction * distance; + MWRender::RayResult result2 = mRendering->castRay(origin, dest, true, true); float dist1 = std::numeric_limits::max(); @@ -4000,4 +4014,35 @@ namespace MWWorld { return mPhysics.get(); } + int World::getActiveWeaponType(void) + { + if (mPlayer) + { + if (mPlayer->getDrawState() == MWMechanics::DrawState_Nothing) + return ESM::Weapon::Type::None; + + if (mPlayer->getDrawState() == MWMechanics::DrawState_Spell) + return ESM::Weapon::Type::Spell; + + MWWorld::Ptr ptr = mPlayer->getPlayer(); + const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); + MWWorld::ConstContainerStoreIterator it = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (it != invStore.end()) + { + if (it->getTypeName() == typeid(ESM::Weapon).name()) + return ESM::Weapon::Type(it->get()->mBase->mData.mType); + if (it->getTypeName() == typeid(ESM::Lockpick).name()) + return ESM::Weapon::Type::PickProbe; + if (it->getTypeName() == typeid(ESM::Probe).name()) + return ESM::Weapon::Type::PickProbe; + } + return ESM::Weapon::Type::HandToHand; + } + return ESM::Weapon::Type::None; + } + + void World::toggleWaterRTT(bool enable) + { + mRendering->toggleWaterRTT(enable); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index eeda6b960..983376f76 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -745,6 +745,11 @@ namespace MWWorld MWPhysics::PhysicsSystem* getPhysicsSystem(void) override; + + /// @Return ESM::Weapon::Type enum describing the type of weapon currently drawn by the player. + int getActiveWeaponType(void) override; + + void toggleWaterRTT(bool enable) override; }; }