mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-02-01 06:15:32 +00:00
Improvements on combat
This commit is contained in:
parent
893b75d767
commit
68c75f66eb
29 changed files with 794 additions and 394 deletions
|
@ -240,7 +240,7 @@ if (WIN32)
|
||||||
# Get rid of useless crud from windows.h
|
# Get rid of useless crud from windows.h
|
||||||
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
|
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")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd26812")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -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) = 0;
|
||||||
virtual float getTargetObject(MWRender::RayResult& result, osg::Transform* pointer, float maxDistance, bool ignorePlayer) = 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 MWPhysics::PhysicsSystem* getPhysicsSystem(void) = 0;
|
||||||
|
|
||||||
|
virtual void toggleWaterRTT(bool enable) = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,10 +48,6 @@
|
||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
#include "spellcasting.hpp"
|
#include "spellcasting.hpp"
|
||||||
|
|
||||||
#ifdef USE_OPENXR
|
|
||||||
#include "../mwvr/vranimation.hpp"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
@ -454,7 +454,6 @@ namespace MWPhysics
|
||||||
osg::Vec3 moved = newPosition - position;
|
osg::Vec3 moved = newPosition - position;
|
||||||
inputManager->mHeadOffset.x() -= moved.x();
|
inputManager->mHeadOffset.x() -= moved.x();
|
||||||
inputManager->mHeadOffset.y() -= moved.y();
|
inputManager->mHeadOffset.y() -= moved.y();
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority
|
#include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority
|
||||||
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
|
||||||
#include "vismask.hpp"
|
#include "vismask.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
@ -895,6 +896,13 @@ namespace MWRender
|
||||||
return;
|
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();
|
AnimStateMap::iterator stateiter = mStates.begin();
|
||||||
while(stateiter != mStates.end())
|
while(stateiter != mStates.end())
|
||||||
{
|
{
|
||||||
|
@ -1053,8 +1061,65 @@ namespace MWRender
|
||||||
return mNodeMap;
|
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<std::string>;
|
||||||
|
using GroupOverrides = std::map<std::string, Overrides>;
|
||||||
|
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()
|
void Animation::resetActiveGroups()
|
||||||
{
|
{
|
||||||
|
const bool isPlayer = (mPtr == MWMechanics::getPlayer());
|
||||||
|
|
||||||
// remove all previous external controllers from the scene graph
|
// remove all previous external controllers from the scene graph
|
||||||
for (ControllerMap::iterator it = mActiveControllers.begin(); it != mActiveControllers.end(); ++it)
|
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)
|
for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it)
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Node> node = getNodeMap().at(it->first); // this should not throw, we already checked for the node existing in addAnimSource
|
osg::ref_ptr<osg::Node> node = getNodeMap().at(it->first); // this should not throw, we already checked for the node existing in addAnimSource
|
||||||
|
if(!isPlayer || !vrOverride(active->first, it->first))
|
||||||
node->addUpdateCallback(it->second);
|
node->addUpdateCallback(it->second);
|
||||||
mActiveControllers.insert(std::make_pair(node, 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;
|
mAccumCtrl = it->second;
|
||||||
|
|
||||||
|
|
|
@ -163,10 +163,6 @@ namespace MWRender
|
||||||
setYaw(yaw);
|
setYaw(yaw);
|
||||||
setPitch(pitch);
|
setPitch(pitch);
|
||||||
setRoll(roll);
|
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)
|
void Camera::attachTo(const MWWorld::Ptr &ptr)
|
||||||
|
@ -431,7 +427,7 @@ namespace MWRender
|
||||||
mTrackingNode = findRootVisitor.mFoundNode;
|
mTrackingNode = findRootVisitor.mFoundNode;
|
||||||
|
|
||||||
if (!mTrackingNode)
|
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;
|
mHeightScale = 1.f;
|
||||||
#else
|
#else
|
||||||
if(isFirstPerson())
|
if(isFirstPerson())
|
||||||
|
@ -452,7 +448,7 @@ namespace MWRender
|
||||||
else
|
else
|
||||||
mHeightScale = 1.f;
|
mHeightScale = 1.f;
|
||||||
}
|
}
|
||||||
rotateCamera(getPitch(), getYaw(), false);
|
rotateCamera(getPitch(), 0.f, getYaw(), false);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,7 @@ public:
|
||||||
VM_Normal,
|
VM_Normal,
|
||||||
VM_FirstPerson,
|
VM_FirstPerson,
|
||||||
VM_HeadOnly,
|
VM_HeadOnly,
|
||||||
#ifdef USE_OPENXR
|
|
||||||
VM_VRHeadless,
|
VM_VRHeadless,
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -1093,7 +1093,7 @@ namespace MWRender
|
||||||
{
|
{
|
||||||
osg::Matrix worldMatrix = osg::computeLocalToWorld(source->getParentalNodePaths()[0]);
|
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();
|
direction.normalize();
|
||||||
|
|
||||||
osg::Vec3f raySource = worldMatrix.getTrans();
|
osg::Vec3f raySource = worldMatrix.getTrans();
|
||||||
|
@ -1469,6 +1469,11 @@ namespace MWRender
|
||||||
mNavMeshNumber = value;
|
mNavMeshNumber = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RenderingManager::toggleWaterRTT(bool enable)
|
||||||
|
{
|
||||||
|
mWater->toggleRTT(enable);
|
||||||
|
}
|
||||||
|
|
||||||
void RenderingManager::updateNavMesh()
|
void RenderingManager::updateNavMesh()
|
||||||
{
|
{
|
||||||
if (!mNavMesh->isEnabled())
|
if (!mNavMesh->isEnabled())
|
||||||
|
|
|
@ -242,6 +242,8 @@ namespace MWRender
|
||||||
|
|
||||||
void setNavMeshNumber(const std::size_t value);
|
void setNavMeshNumber(const std::size_t value);
|
||||||
|
|
||||||
|
void toggleWaterRTT(bool enable);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateProjectionMatrix();
|
void updateProjectionMatrix();
|
||||||
void updateTextureFiltering();
|
void updateTextureFiltering();
|
||||||
|
|
|
@ -436,6 +436,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem
|
||||||
, mToggled(true)
|
, mToggled(true)
|
||||||
, mTop(0)
|
, mTop(0)
|
||||||
, mInterior(false)
|
, mInterior(false)
|
||||||
|
, mRTTToggled(true)
|
||||||
{
|
{
|
||||||
mSimulation.reset(new RippleSimulation(mSceneRoot, resourceSystem));
|
mSimulation.reset(new RippleSimulation(mSceneRoot, resourceSystem));
|
||||||
|
|
||||||
|
@ -720,6 +721,20 @@ bool Water::toggle()
|
||||||
return mToggled;
|
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
|
bool Water::isUnderwater(const osg::Vec3f &pos) const
|
||||||
{
|
{
|
||||||
return pos.z() < mTop && mToggled && mEnabled;
|
return pos.z() < mTop && mToggled && mEnabled;
|
||||||
|
|
|
@ -71,6 +71,8 @@ namespace MWRender
|
||||||
float mTop;
|
float mTop;
|
||||||
bool mInterior;
|
bool mInterior;
|
||||||
|
|
||||||
|
bool mRTTToggled;
|
||||||
|
|
||||||
osg::Vec3f getSceneNodeCoordinates(int gridX, int gridY);
|
osg::Vec3f getSceneNodeCoordinates(int gridX, int gridY);
|
||||||
void updateVisible();
|
void updateVisible();
|
||||||
|
|
||||||
|
@ -94,6 +96,9 @@ namespace MWRender
|
||||||
|
|
||||||
bool toggle();
|
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;
|
bool isUnderwater(const osg::Vec3f& pos) const;
|
||||||
|
|
||||||
/// adds an emitter, position will be tracked automatically using its scene node
|
/// adds an emitter, position will be tracked automatically using its scene node
|
||||||
|
|
|
@ -17,6 +17,11 @@
|
||||||
#include "../mwmechanics/combat.hpp"
|
#include "../mwmechanics/combat.hpp"
|
||||||
#include "../mwmechanics/weapontype.hpp"
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
|
#ifdef USE_OPENXR
|
||||||
|
#include "../mwvr/vrenvironment.hpp"
|
||||||
|
#include "../mwvr/vranimation.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "animation.hpp"
|
#include "animation.hpp"
|
||||||
#include "rotatecontroller.hpp"
|
#include "rotatecontroller.hpp"
|
||||||
|
|
||||||
|
@ -107,9 +112,16 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength)
|
||||||
if (weapon->getTypeName() != typeid(ESM::Weapon).name())
|
if (weapon->getTypeName() != typeid(ESM::Weapon).name())
|
||||||
return;
|
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.
|
// 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 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));
|
* osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
|
||||||
|
#endif
|
||||||
|
|
||||||
const MWWorld::Store<ESM::GameSetting> &gmst =
|
const MWWorld::Store<ESM::GameSetting> &gmst =
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||||
|
|
|
@ -760,12 +760,17 @@ void OpenXRInputManager::updateActivationIndication(void)
|
||||||
bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode();
|
bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode();
|
||||||
bool show = guiMode | mActivationIndication;
|
bool show = guiMode | mActivationIndication;
|
||||||
if (mPlayer)
|
if (mPlayer)
|
||||||
|
{
|
||||||
|
if (show != mPlayer->getPointing())
|
||||||
|
{
|
||||||
mPlayer->setPointing(show);
|
mPlayer->setPointing(show);
|
||||||
|
|
||||||
auto* playerAnimation = Environment::get().getPlayerAnimation();
|
auto* playerAnimation = Environment::get().getPlayerAnimation();
|
||||||
if (playerAnimation)
|
if (playerAnimation)
|
||||||
playerAnimation->setPointForward(show);
|
playerAnimation->setPointForward(show);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -938,8 +943,11 @@ private:
|
||||||
auto player = mPlayer->getPlayer();
|
auto player = mPlayer->getPlayer();
|
||||||
if (!mRealisticCombat || mRealisticCombat->ptr != player)
|
if (!mRealisticCombat || mRealisticCombat->ptr != player)
|
||||||
mRealisticCombat.reset(new RealisticCombat::StateMachine(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)
|
void OpenXRInputManager::processEvent(const OpenXRActionEvent& event)
|
||||||
|
@ -1112,7 +1120,7 @@ private:
|
||||||
|
|
||||||
auto* xr = Environment::get().getManager();
|
auto* xr = Environment::get().getManager();
|
||||||
auto* session = Environment::get().getSession();
|
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);
|
xr->playerScale(currentHeadPose);
|
||||||
currentHeadPose.position *= Environment::get().unitsPerMeter();
|
currentHeadPose.position *= Environment::get().unitsPerMeter();
|
||||||
osg::Vec3 vrMovement = currentHeadPose.position - mPreviousHeadPose.position;
|
osg::Vec3 vrMovement = currentHeadPose.position - mPreviousHeadPose.position;
|
||||||
|
@ -1141,8 +1149,6 @@ private:
|
||||||
mVrAngles[1] = roll;
|
mVrAngles[1] = roll;
|
||||||
mVrAngles[2] = yaw;
|
mVrAngles[2] = yaw;
|
||||||
world->rotateObject(player, mVrAngles[0], mVrAngles[1], mVrAngles[2], MWBase::RotationFlag_none);
|
world->rotateObject(player, mVrAngles[0], mVrAngles[1], mVrAngles[2], MWBase::RotationFlag_none);
|
||||||
|
|
||||||
MWBase::Environment::get().getWorld()->getRenderingManager().getCamera()->updateCamera();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,34 +48,49 @@ namespace MWVR
|
||||||
|
|
||||||
auto* xr = Environment::get().getManager();
|
auto* xr = Environment::get().getManager();
|
||||||
|
|
||||||
if (!xr->sessionRunning())
|
if (!mShouldRender)
|
||||||
return;
|
return;
|
||||||
if (!mPredictionsReady)
|
|
||||||
|
if (mRenderedFrames != mRenderFrame - 1)
|
||||||
|
{
|
||||||
|
Log(Debug::Warning) << "swapBuffers called out of order";
|
||||||
|
waitFrame();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto layer : mLayerStack.layerObjects())
|
for (auto layer : mLayerStack.layerObjects())
|
||||||
if(layer)
|
if(layer)
|
||||||
layer->swapBuffers(gc);
|
layer->swapBuffers(gc);
|
||||||
|
|
||||||
timer.checkpoint("Rendered");
|
timer.checkpoint("Rendered");
|
||||||
|
|
||||||
xr->endFrame(xr->impl().frameState().predictedDisplayTime, &mLayerStack);
|
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()
|
void OpenXRSession::waitFrame()
|
||||||
{
|
{
|
||||||
|
if(mPredictedFrames < mPredictionFrame)
|
||||||
|
{
|
||||||
|
Timer timer("OpenXRSession::waitFrame");
|
||||||
auto* xr = Environment::get().getManager();
|
auto* xr = Environment::get().getManager();
|
||||||
xr->handleEvents();
|
xr->handleEvents();
|
||||||
if (!xr->sessionRunning())
|
mIsRunning = xr->sessionRunning();
|
||||||
|
if (!mIsRunning)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Timer timer("OpenXRSession::waitFrame");
|
|
||||||
xr->waitFrame();
|
xr->waitFrame();
|
||||||
timer.checkpoint("waitFrame");
|
timer.checkpoint("waitFrame");
|
||||||
predictNext(0);
|
predictNext(0);
|
||||||
|
|
||||||
mPredictionsReady = true;
|
mPredictedFrames++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -118,7 +133,7 @@ namespace MWVR
|
||||||
|
|
||||||
float OpenXRSession::movementYaw(void)
|
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 yaw = 0.f;
|
||||||
float pitch = 0.f;
|
float pitch = 0.f;
|
||||||
float roll = 0.f;
|
float roll = 0.f;
|
||||||
|
@ -126,25 +141,42 @@ namespace MWVR
|
||||||
return yaw;
|
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)
|
void OpenXRSession::predictNext(int extraPeriods)
|
||||||
{
|
{
|
||||||
auto* xr = Environment::get().getManager();
|
auto* xr = Environment::get().getManager();
|
||||||
auto* input = Environment::get().getInputManager();
|
auto* input = Environment::get().getInputManager();
|
||||||
auto mPredictedDisplayTime = xr->impl().frameState().predictedDisplayTime;
|
auto mPredictedDisplayTime = xr->impl().frameState().predictedDisplayTime;
|
||||||
|
|
||||||
auto previousHeadPose = mPredictedPoses.head[(int)TrackedSpace::STAGE];
|
PoseSets newPoses{};
|
||||||
|
newPoses.head[(int)TrackedSpace::STAGE] = xr->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::STAGE);
|
||||||
// Update pose predictions
|
newPoses.head[(int)TrackedSpace::VIEW] = xr->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::VIEW);
|
||||||
mPredictedPoses.head[(int)TrackedSpace::STAGE] = xr->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::STAGE);
|
newPoses.hands[(int)TrackedSpace::STAGE] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::STAGE);
|
||||||
mPredictedPoses.head[(int)TrackedSpace::VIEW] = xr->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::VIEW);
|
newPoses.hands[(int)TrackedSpace::VIEW] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::VIEW);
|
||||||
mPredictedPoses.hands[(int)TrackedSpace::STAGE] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::STAGE);
|
|
||||||
mPredictedPoses.hands[(int)TrackedSpace::VIEW] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::VIEW);
|
|
||||||
auto stageViews = xr->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::STAGE);
|
auto stageViews = xr->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::STAGE);
|
||||||
auto hmdViews = xr->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::VIEW);
|
auto hmdViews = xr->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::VIEW);
|
||||||
mPredictedPoses.eye[(int)TrackedSpace::STAGE][(int)Side::LEFT_HAND] = fromXR(stageViews[(int)Side::LEFT_HAND].pose);
|
newPoses.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);
|
newPoses.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);
|
newPoses.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::VIEW][(int)Side::RIGHT_HAND] = fromXR(hmdViews[(int)Side::RIGHT_HAND].pose);
|
||||||
|
mPredictedPoses[(int)PredictionSlice::Predraw] = newPoses;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,19 @@ extern void getEulerAngles(const osg::Quat& quat, float& yaw, float& pitch, floa
|
||||||
|
|
||||||
class OpenXRSession
|
class OpenXRSession
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
using seconds = std::chrono::duration<double>;
|
using seconds = std::chrono::duration<double>;
|
||||||
using nanoseconds = std::chrono::nanoseconds;
|
using nanoseconds = std::chrono::nanoseconds;
|
||||||
using clock = std::chrono::steady_clock;
|
using clock = std::chrono::steady_clock;
|
||||||
using time_point = clock::time_point;
|
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:
|
public:
|
||||||
OpenXRSession();
|
OpenXRSession();
|
||||||
~OpenXRSession();
|
~OpenXRSession();
|
||||||
|
@ -31,7 +39,7 @@ public:
|
||||||
void setLayer(OpenXRLayerStack::Layer layerType, OpenXRLayer* layer);
|
void setLayer(OpenXRLayerStack::Layer layerType, OpenXRLayer* layer);
|
||||||
void swapBuffers(osg::GraphicsContext* gc);
|
void swapBuffers(osg::GraphicsContext* gc);
|
||||||
|
|
||||||
const PoseSets& predictedPoses() const { return mPredictedPoses; };
|
const PoseSets& predictedPoses(PredictionSlice slice);
|
||||||
|
|
||||||
//! Call before updating poses and other inputs
|
//! Call before updating poses and other inputs
|
||||||
void waitFrame();
|
void waitFrame();
|
||||||
|
@ -42,10 +50,22 @@ public:
|
||||||
//! Yaw angle to be used for offsetting movement direction
|
//! Yaw angle to be used for offsetting movement direction
|
||||||
float movementYaw(void);
|
float movementYaw(void);
|
||||||
|
|
||||||
|
void advanceFrame(void);
|
||||||
|
|
||||||
|
bool isRunning() { return mIsRunning; }
|
||||||
|
bool shouldRender() { return mShouldRender; }
|
||||||
|
|
||||||
OpenXRLayerStack mLayerStack{};
|
OpenXRLayerStack mLayerStack{};
|
||||||
|
|
||||||
PoseSets mPredictedPoses{};
|
PoseSets mPredictedPoses[(int)PredictionSlice::NumSlices]{};
|
||||||
bool mPredictionsReady{ false };
|
|
||||||
|
int mRenderFrame{ 0 };
|
||||||
|
int mRenderedFrames{ 0 };
|
||||||
|
int mPredictionFrame{ 1 };
|
||||||
|
int mPredictedFrames{ 0 };
|
||||||
|
|
||||||
|
bool mIsRunning{ false };
|
||||||
|
bool mShouldRender{ false };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,10 +125,6 @@ namespace MWVR {
|
||||||
Timer timer("Swapchain::endFrame");
|
Timer timer("Swapchain::endFrame");
|
||||||
// Blit frame to swapchain
|
// Blit frame to swapchain
|
||||||
|
|
||||||
if (!mXR->sessionRunning())
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
|
|
||||||
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
|
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
|
||||||
uint32_t swapchainImageIndex = 0;
|
uint32_t swapchainImageIndex = 0;
|
||||||
CHECK_XRCMD(xrAcquireSwapchainImage(mSwapchain, &acquireInfo, &swapchainImageIndex));
|
CHECK_XRCMD(xrAcquireSwapchainImage(mSwapchain, &acquireInfo, &swapchainImageIndex));
|
||||||
|
|
|
@ -2,6 +2,11 @@
|
||||||
#include "openxrmanager.hpp"
|
#include "openxrmanager.hpp"
|
||||||
#include "openxrmanagerimpl.hpp"
|
#include "openxrmanagerimpl.hpp"
|
||||||
#include "../mwinput/inputmanagerimp.hpp"
|
#include "../mwinput/inputmanagerimp.hpp"
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
#include "../mwrender/renderingmanager.hpp"
|
||||||
|
#include "../mwrender/water.hpp"
|
||||||
|
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
||||||
|
@ -46,6 +51,7 @@ namespace MWVR {
|
||||||
camera->setGraphicsContext(gc);
|
camera->setGraphicsContext(gc);
|
||||||
|
|
||||||
camera->setInitialDrawCallback(new OpenXRView::InitialDrawCallback());
|
camera->setInitialDrawCallback(new OpenXRView::InitialDrawCallback());
|
||||||
|
camera->setFinalDrawCallback(new OpenXRView::FinalDrawCallback());
|
||||||
|
|
||||||
return camera.release();
|
return camera.release();
|
||||||
}
|
}
|
||||||
|
@ -57,13 +63,18 @@ namespace MWVR {
|
||||||
mSwapchain->beginFrame(renderInfo.getState()->getGraphicsContext());
|
mSwapchain->beginFrame(renderInfo.getState()->getGraphicsContext());
|
||||||
}
|
}
|
||||||
mTimer.checkpoint("Prerender");
|
mTimer.checkpoint("Prerender");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenXRView::postrenderCallback(osg::RenderInfo& renderInfo)
|
void OpenXRView::postrenderCallback(osg::RenderInfo& renderInfo)
|
||||||
{
|
{
|
||||||
|
auto name = renderInfo.getCurrentCamera()->getName();
|
||||||
mTimer.checkpoint("Postrender");
|
mTimer.checkpoint("Postrender");
|
||||||
auto state = renderInfo.getState();
|
// Water RTT happens before the initial draw callback,
|
||||||
auto gl = osg::GLExtensions::Get(state->getContextID(), false);
|
// 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)
|
void OpenXRView::swapBuffers(osg::GraphicsContext* gc)
|
||||||
|
|
|
@ -19,6 +19,11 @@ namespace MWVR
|
||||||
public:
|
public:
|
||||||
virtual void operator()(osg::RenderInfo& renderInfo) const;
|
virtual void operator()(osg::RenderInfo& renderInfo) const;
|
||||||
};
|
};
|
||||||
|
class FinalDrawCallback : public osg::Camera::DrawCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void operator()(osg::RenderInfo& renderInfo) const;
|
||||||
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
OpenXRView(osg::ref_ptr<OpenXRManager> XR, std::string name, OpenXRSwapchain::Config config, osg::ref_ptr<osg::State> state);
|
OpenXRView(osg::ref_ptr<OpenXRManager> XR, std::string name, OpenXRSwapchain::Config config, osg::ref_ptr<osg::State> state);
|
||||||
|
|
|
@ -31,16 +31,6 @@ namespace MWVR
|
||||||
mCompositionLayerProjectionViews[0].pose.orientation.w = 1;
|
mCompositionLayerProjectionViews[0].pose.orientation.w = 1;
|
||||||
mCompositionLayerProjectionViews[1].pose.orientation.w = 1;
|
mCompositionLayerProjectionViews[1].pose.orientation.w = 1;
|
||||||
this->setName("OpenXRRoot");
|
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)
|
OpenXRViewer::~OpenXRViewer(void)
|
||||||
|
@ -64,8 +54,6 @@ namespace MWVR
|
||||||
|
|
||||||
void OpenXRViewer::traversals()
|
void OpenXRViewer::traversals()
|
||||||
{
|
{
|
||||||
auto* xr = Environment::get().getManager();
|
|
||||||
xr->handleEvents();
|
|
||||||
mViewer->updateTraversal();
|
mViewer->updateTraversal();
|
||||||
mViewer->renderingTraversals();
|
mViewer->renderingTraversals();
|
||||||
}
|
}
|
||||||
|
@ -256,8 +244,10 @@ namespace MWVR
|
||||||
rightView->swapBuffers(gc);
|
rightView->swapBuffers(gc);
|
||||||
timer.checkpoint("Views");
|
timer.checkpoint("Views");
|
||||||
|
|
||||||
mCompositionLayerProjectionViews[0].pose = toXR(session->predictedPoses().eye[(int)TrackedSpace::STAGE][(int)Side::LEFT_HAND]);
|
auto& drawPoses = session->predictedPoses(OpenXRSession::PredictionSlice::Draw);
|
||||||
mCompositionLayerProjectionViews[1].pose = toXR(session->predictedPoses().eye[(int)TrackedSpace::STAGE][(int)Side::RIGHT_HAND]);
|
|
||||||
|
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");
|
timer.checkpoint("Poses");
|
||||||
|
|
||||||
|
@ -304,13 +294,7 @@ namespace MWVR
|
||||||
auto& view = mViews[name];
|
auto& view = mViews[name];
|
||||||
|
|
||||||
if (name == "LeftEye")
|
if (name == "LeftEye")
|
||||||
{
|
Environment::get().getSession()->advanceFrame();
|
||||||
auto* xr = Environment::get().getManager();
|
|
||||||
if (xr->sessionRunning())
|
|
||||||
{
|
|
||||||
xr->beginFrame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
view->prerenderCallback(info);
|
view->prerenderCallback(info);
|
||||||
}
|
}
|
||||||
|
@ -330,86 +314,4 @@ namespace MWVR
|
||||||
Log(Debug::Warning) << ("osg overwrote predraw");
|
Log(Debug::Warning) << ("osg overwrote predraw");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenXRViewer::updateTransformNode(osg::Object* object, osg::Object* data)
|
|
||||||
{
|
|
||||||
auto* hand_transform = dynamic_cast<SceneUtil::PositionAttitudeTransform*>(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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,17 +70,6 @@ namespace MWVR
|
||||||
OpenXRViewer* mViewer;
|
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:
|
public:
|
||||||
OpenXRViewer(
|
OpenXRViewer(
|
||||||
osg::ref_ptr<osgViewer::Viewer> viewer);
|
osg::ref_ptr<osgViewer::Viewer> viewer);
|
||||||
|
@ -111,10 +100,6 @@ namespace MWVR
|
||||||
osg::ref_ptr<osgViewer::Viewer> mViewer = nullptr;
|
osg::ref_ptr<osgViewer::Viewer> mViewer = nullptr;
|
||||||
std::map<std::string, osg::ref_ptr<OpenXRView> > mViews{};
|
std::map<std::string, osg::ref_ptr<OpenXRView> > mViews{};
|
||||||
std::map<std::string, osg::ref_ptr<osg::Camera> > mCameras{};
|
std::map<std::string, osg::ref_ptr<osg::Camera> > mCameras{};
|
||||||
|
|
||||||
SceneUtil::PositionAttitudeTransform* mLeftHandTransform = new SceneUtil::PositionAttitudeTransform();
|
|
||||||
SceneUtil::PositionAttitudeTransform* mRightHandTransform = new SceneUtil::PositionAttitudeTransform();
|
|
||||||
|
|
||||||
PredrawCallback* mPreDraw{ nullptr };
|
PredrawCallback* mPreDraw{ nullptr };
|
||||||
PostdrawCallback* mPostDraw{ nullptr };
|
PostdrawCallback* mPostDraw{ nullptr };
|
||||||
|
|
||||||
|
|
|
@ -143,6 +143,12 @@ namespace MWVR
|
||||||
// Disable normal OSG FBO camera setup because it will undo the MSAA FBO configuration.
|
// Disable normal OSG FBO camera setup because it will undo the MSAA FBO configuration.
|
||||||
renderer->setCameraRequiresSetUp(false);
|
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
|
void
|
||||||
|
@ -151,37 +157,27 @@ namespace MWVR
|
||||||
osg::View::Slave& slave)
|
osg::View::Slave& slave)
|
||||||
{
|
{
|
||||||
mView->mTimer.checkpoint("UpdateSlave");
|
mView->mTimer.checkpoint("UpdateSlave");
|
||||||
|
|
||||||
auto* camera = slave._camera.get();
|
auto* camera = slave._camera.get();
|
||||||
auto name = camera->getName();
|
auto name = camera->getName();
|
||||||
|
|
||||||
auto& poses = mSession->predictedPoses();
|
int side = 0;
|
||||||
|
|
||||||
Pose viewPose{};
|
|
||||||
Pose stagePose{};
|
|
||||||
|
|
||||||
if (name == "LeftEye")
|
if (name == "LeftEye")
|
||||||
{
|
{
|
||||||
mSession->waitFrame();
|
side = (int)Side::LEFT_HAND;
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
stagePose = poses.eye[(int)TrackedSpace::STAGE][(int)Side::RIGHT_HAND];
|
side = (int)Side::RIGHT_HAND;
|
||||||
viewPose = poses.eye[(int)TrackedSpace::VIEW][(int)Side::RIGHT_HAND];
|
|
||||||
mView->setPredictedPose(viewPose);
|
|
||||||
}
|
}
|
||||||
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 viewMatrix = view.getCamera()->getViewMatrix();
|
||||||
auto modifiedViewMatrix = viewMatrix * mView->viewMatrix();
|
auto modifiedViewMatrix = viewMatrix * mView->viewMatrix();
|
||||||
|
|
|
@ -1,17 +1,41 @@
|
||||||
#include "realisticcombat.hpp"
|
#include "realisticcombat.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/soundmanager.hpp"
|
#include "../mwbase/soundmanager.hpp"
|
||||||
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
namespace MWVR { namespace RealisticCombat {
|
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) {}
|
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
|
// Actions common to all transitions
|
||||||
void StateMachine::transition(
|
void StateMachine::transition(
|
||||||
SwingState newState)
|
SwingState newState)
|
||||||
{
|
{
|
||||||
if (newState == state)
|
Log(Debug::Verbose) << "Transition: " << stateToString(state) << " -> " << stateToString(newState);
|
||||||
throw std::logic_error("Cannot transition to current state");
|
|
||||||
|
|
||||||
maxSwingVelocity = 0.f;
|
maxSwingVelocity = 0.f;
|
||||||
timeSinceEnteredState = 0.f;
|
timeSinceEnteredState = 0.f;
|
||||||
|
@ -28,11 +52,28 @@ void StateMachine::reset()
|
||||||
state = SwingState_Ready;
|
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)
|
void StateMachine::update(float dt, bool enabled)
|
||||||
{
|
{
|
||||||
auto* session = Environment::get().getSession();
|
auto* session = Environment::get().getSession();
|
||||||
auto& handPose = session->predictedPoses().hands[(int)MWVR::TrackedSpace::STAGE][(int)MWVR::Side::RIGHT_HAND];
|
auto* world = MWBase::Environment::get().getWorld();
|
||||||
auto& headPose = session->predictedPoses().head[(int)MWVR::TrackedSpace::STAGE];
|
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)
|
if (mEnabled != enabled)
|
||||||
{
|
{
|
||||||
|
@ -47,17 +88,22 @@ void StateMachine::update(float dt, bool enabled)
|
||||||
|
|
||||||
// First determine direction of different swing types
|
// 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
|
// Discover orientation of weapon
|
||||||
osg::Vec3 thrustDirection = handPose.position - headPose.position;
|
osg::Quat weaponDir = handPose.orientation;
|
||||||
thrustDirection.z() = 0;
|
|
||||||
thrustDirection.normalize();
|
|
||||||
|
|
||||||
// Chop is straight down
|
// Morrowind models do not hold weapons at a natural angle, so i rotate the hand forward
|
||||||
osg::Vec3 chopDirection = osg::Vec3(0.f, 0.f, -1.f);
|
// 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
|
// Thrust means stabbing in the direction of the weapon
|
||||||
osg::Vec3 slashDirection = chopDirection ^ thrustDirection;
|
osg::Vec3 thrustDirection = weaponDir * osg::Vec3{ 0,1,0 };
|
||||||
slashDirection.normalize();
|
|
||||||
|
// 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
|
// Next determine current hand movement
|
||||||
|
@ -72,37 +118,36 @@ void StateMachine::update(float dt, bool enabled)
|
||||||
osg::Vec3 movement = handPose.position - previousPosition;
|
osg::Vec3 movement = handPose.position - previousPosition;
|
||||||
movementSinceEnteredState += movement.length();
|
movementSinceEnteredState += movement.length();
|
||||||
previousPosition = handPose.position;
|
previousPosition = handPose.position;
|
||||||
osg::Vec3 swingDirection = movement / dt;
|
osg::Vec3 swingVector = movement / dt;
|
||||||
|
|
||||||
// Compute swing velocity
|
// Compute swing velocity
|
||||||
// Unidirectional
|
// Unidirectional
|
||||||
thrustVelocity = swingDirection * thrustDirection;
|
thrustVelocity = swingVector * thrustDirection;
|
||||||
// Bidirectional
|
// Bidirectional
|
||||||
slashVelocity = std::abs(swingDirection * slashDirection);
|
slashVelocity = std::abs(swingVector * slashDirection);
|
||||||
chopVelocity = std::abs(swingDirection * chopDirection);
|
chopVelocity = std::abs(swingVector * chopDirection);
|
||||||
|
velocity = swingVector.length();
|
||||||
|
|
||||||
// Pick swing type based on greatest current velocity
|
// Pick swing type based on greatest current velocity
|
||||||
// Note i use abs() of thrust velocity to prevent accidentally triggering
|
// Note i use abs() of thrust velocity to prevent accidentally triggering
|
||||||
// chop or slash when player is withdrawing his limb.
|
// chop or slash when player is withdrawing his limb.
|
||||||
if (std::abs(thrustVelocity) > slashVelocity && std::abs(thrustVelocity) > chopVelocity)
|
if (std::abs(thrustVelocity) > slashVelocity && std::abs(thrustVelocity) > chopVelocity)
|
||||||
{
|
{
|
||||||
velocity = thrustVelocity;
|
|
||||||
swingType = ESM::Weapon::AT_Thrust;
|
swingType = ESM::Weapon::AT_Thrust;
|
||||||
}
|
}
|
||||||
else if (slashVelocity > chopVelocity)
|
else if (slashVelocity > chopVelocity)
|
||||||
{
|
{
|
||||||
velocity = slashVelocity;
|
|
||||||
swingType = ESM::Weapon::AT_Slash;
|
swingType = ESM::Weapon::AT_Slash;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
velocity = chopVelocity;
|
|
||||||
swingType = ESM::Weapon::AT_Chop;
|
swingType = ESM::Weapon::AT_Chop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
|
case SwingState_Idle:
|
||||||
|
return update_idleState();
|
||||||
case SwingState_Ready:
|
case SwingState_Ready:
|
||||||
return update_readyState();
|
return update_readyState();
|
||||||
case SwingState_Swinging:
|
case SwingState_Swinging:
|
||||||
|
@ -112,10 +157,20 @@ 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()
|
void StateMachine::update_readyState()
|
||||||
{
|
{
|
||||||
if (velocity >= minVelocity)
|
if (canSwing())
|
||||||
if(timeSinceEnteredState >= minimumPeriod)
|
|
||||||
return transition_readyToSwinging();
|
return transition_readyToSwinging();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,8 +179,9 @@ void StateMachine::transition_readyToSwinging()
|
||||||
shouldSwish = true;
|
shouldSwish = true;
|
||||||
transition(SwingState_Swinging);
|
transition(SwingState_Swinging);
|
||||||
|
|
||||||
// As an exception, update the new state immediately to allow
|
// As a special case, update the new state immediately to allow
|
||||||
// same-frame impacts.
|
// same-frame impacts. This is important if the player is moving
|
||||||
|
// at a velocity close to the minimum velocity.
|
||||||
update_swingingState();
|
update_swingingState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,47 +207,47 @@ void StateMachine::update_swingingState()
|
||||||
maxSwingVelocity = std::max(velocity, maxSwingVelocity);
|
maxSwingVelocity = std::max(velocity, maxSwingVelocity);
|
||||||
strength = std::min(1.f, (maxSwingVelocity - minVelocity) / maxVelocity);
|
strength = std::min(1.f, (maxSwingVelocity - minVelocity) / maxVelocity);
|
||||||
|
|
||||||
if (velocity < minVelocity)
|
// Require a minimum movement of the hand before a hit can be made
|
||||||
return transition_swingingToReady();
|
// This is to prevent microswings
|
||||||
|
|
||||||
// Require a minimum period of swinging before a hit can be made
|
|
||||||
// This is to prevent annoying little microswings
|
|
||||||
if (movementSinceEnteredState > minimumPeriod)
|
if (movementSinceEnteredState > minimumPeriod)
|
||||||
{
|
{
|
||||||
playSwish();
|
playSwish();
|
||||||
|
if (// When velocity falls below minimum, register the miss
|
||||||
// Note: calling hit with simulated=true to avoid side effects
|
!canSwing()
|
||||||
if (ptr.getClass().hit(ptr, strength, swingType, true))
|
// Call hit with simulated=true to check for hit
|
||||||
|
|| ptr.getClass().hit(ptr, strength, swingType, true)
|
||||||
|
)
|
||||||
return transition_swingingToImpact();
|
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()
|
void StateMachine::transition_swingingToReady()
|
||||||
{
|
{
|
||||||
if (movementSinceEnteredState > minimumPeriod)
|
|
||||||
{
|
|
||||||
playSwish();
|
|
||||||
ptr.getClass().hit(ptr, strength, swingType, false);
|
|
||||||
}
|
|
||||||
transition(SwingState_Ready);
|
transition(SwingState_Ready);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateMachine::transition_swingingToImpact()
|
void StateMachine::transition_swingingToImpact()
|
||||||
{
|
{
|
||||||
playSwish();
|
|
||||||
ptr.getClass().hit(ptr, strength, swingType, false);
|
ptr.getClass().hit(ptr, strength, swingType, false);
|
||||||
transition(SwingState_Impact);
|
transition(SwingState_Impact);
|
||||||
|
std::cout << "Transition: Swing -> Impact" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateMachine::update_impactState()
|
void StateMachine::update_impactState()
|
||||||
{
|
{
|
||||||
if (velocity < minVelocity)
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -13,9 +13,10 @@
|
||||||
namespace MWVR { namespace RealisticCombat {
|
namespace MWVR { namespace RealisticCombat {
|
||||||
enum SwingState
|
enum SwingState
|
||||||
{
|
{
|
||||||
SwingState_Ready = 0,
|
SwingState_Idle = 0,
|
||||||
SwingState_Swinging = 1,
|
SwingState_Ready = 1,
|
||||||
SwingState_Impact = 2
|
SwingState_Swinging = 2,
|
||||||
|
SwingState_Impact = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StateMachine
|
struct StateMachine
|
||||||
|
@ -27,7 +28,7 @@ struct StateMachine
|
||||||
float velocity = 0.f;
|
float velocity = 0.f;
|
||||||
float maxSwingVelocity = 0.f;
|
float maxSwingVelocity = 0.f;
|
||||||
|
|
||||||
SwingState state = SwingState_Ready;
|
SwingState state = SwingState_Idle;
|
||||||
MWWorld::Ptr ptr = MWWorld::Ptr();
|
MWWorld::Ptr ptr = MWWorld::Ptr();
|
||||||
int swingType = -1;
|
int swingType = -1;
|
||||||
float strength = 0.f;
|
float strength = 0.f;
|
||||||
|
@ -36,7 +37,7 @@ struct StateMachine
|
||||||
float slashVelocity{ 0.f };
|
float slashVelocity{ 0.f };
|
||||||
float chopVelocity{ 0.f };
|
float chopVelocity{ 0.f };
|
||||||
|
|
||||||
float minimumPeriod{ .5f };
|
float minimumPeriod{ .25f };
|
||||||
float timeSinceEnteredState = { 0.f };
|
float timeSinceEnteredState = { 0.f };
|
||||||
float movementSinceEnteredState = { 0.f };
|
float movementSinceEnteredState = { 0.f };
|
||||||
|
|
||||||
|
@ -47,6 +48,8 @@ struct StateMachine
|
||||||
|
|
||||||
StateMachine(MWWorld::Ptr ptr);
|
StateMachine(MWWorld::Ptr ptr);
|
||||||
|
|
||||||
|
bool canSwing();
|
||||||
|
|
||||||
void playSwish();
|
void playSwish();
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
|
@ -54,6 +57,9 @@ struct StateMachine
|
||||||
|
|
||||||
void update(float dt, bool enabled);
|
void update(float dt, bool enabled);
|
||||||
|
|
||||||
|
void update_idleState();
|
||||||
|
void transition_idleToReady();
|
||||||
|
|
||||||
void update_readyState();
|
void update_readyState();
|
||||||
void transition_readyToSwinging();
|
void transition_readyToSwinging();
|
||||||
|
|
||||||
|
@ -62,7 +68,7 @@ struct StateMachine
|
||||||
void transition_swingingToImpact();
|
void transition_swingingToImpact();
|
||||||
|
|
||||||
void update_impactState();
|
void update_impactState();
|
||||||
void transition_impactToReady();
|
void transition_impactToIdle();
|
||||||
};
|
};
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -66,36 +66,33 @@
|
||||||
namespace MWVR
|
namespace MWVR
|
||||||
{
|
{
|
||||||
|
|
||||||
// This will work for a prototype. But finger/arm control might be better implemented using the
|
// Some weapon types, such as spellcast, are classified as melee even though they are not.
|
||||||
// existing animation system, implementing this as an animation source.
|
// All the fake melee types have negative type enum, but also so does hand to hand.
|
||||||
// But I'm not sure it would be since these are not classical animations.
|
// I think this covers all the cases
|
||||||
// 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.
|
static bool isMeleeWeapon(int type)
|
||||||
// But openmw doesn't really have any concepts for user animation overrides as far as i can tell.
|
{
|
||||||
|
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
|
class ForearmController : public osg::NodeCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ForearmController(osg::Node* relativeTo, SceneUtil::PositionAttitudeTransform* tracker);
|
ForearmController() = default;
|
||||||
void setEnabled(bool enabled) { mEnabled = enabled; };
|
void setEnabled(bool enabled) { mEnabled = enabled; };
|
||||||
void operator()(osg::Node* node, osg::NodeVisitor* nv);
|
void operator()(osg::Node* node, osg::NodeVisitor* nv);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool mEnabled = true;
|
bool mEnabled{ true };
|
||||||
osg::Quat mRotate{};
|
|
||||||
osg::Node* mRelativeTo;
|
|
||||||
osg::Matrix mOffset{ osg::Matrix::identity() };
|
|
||||||
bool mOffsetInitialized = false;
|
|
||||||
SceneUtil::PositionAttitudeTransform* mTracker;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ForearmController::ForearmController(osg::Node* relativeTo, SceneUtil::PositionAttitudeTransform* tracker)
|
|
||||||
: mRelativeTo(relativeTo)
|
|
||||||
, mTracker(tracker)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ForearmController::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
void ForearmController::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||||
{
|
{
|
||||||
if (!mEnabled)
|
if (!mEnabled)
|
||||||
|
@ -105,44 +102,102 @@ void ForearmController::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::MatrixTransform* transform = static_cast<osg::MatrixTransform*>(node);
|
osg::MatrixTransform* transform = static_cast<osg::MatrixTransform*>(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.
|
side = (int)Side::LEFT_HAND;
|
||||||
// Trackers track hands, not forearms.
|
// We base ourselves on the world position of the camera
|
||||||
// But i have to transform the forearms to account for deformations,
|
// Ensure it is updated before placing the hands
|
||||||
// so i subtract the hand transform from the final transform to center the hands.
|
// I'm sure this can be achieved properly by managing the scene graph better.
|
||||||
std::string handName = node->getName() == "Bip01 L Forearm" ? "Bip01 L Hand" : "Bip01 R Hand";
|
MWBase::Environment::get().getWorld()->getRenderingManager().getCamera()->updateCamera();
|
||||||
SceneUtil::FindByNameVisitor findHandVisitor(handName);
|
|
||||||
node->accept(findHandVisitor);
|
|
||||||
mOffset = findHandVisitor.mFoundNode->asTransform()->asMatrixTransform()->getInverseMatrix();
|
|
||||||
mOffsetInitialized = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Get current world transform of limb
|
||||||
osg::Matrix worldToLimb = osg::computeLocalToWorld(node->getParentalNodePaths()[0]);
|
osg::Matrix worldToLimb = osg::computeLocalToWorld(node->getParentalNodePaths()[0]);
|
||||||
// Get current world of the reference node
|
// Get current world of the reference node
|
||||||
osg::Matrix worldReference = osg::Matrix::identity();
|
osg::Matrix worldReference = osg::Matrix::identity();
|
||||||
// New transform is reference node + tracker.
|
// New transform based on tracking.
|
||||||
mTracker->computeLocalToWorldMatrix(worldReference, nullptr);
|
worldReference.preMultTranslate(position);
|
||||||
// Get hand
|
worldReference.preMultRotate(orientation);
|
||||||
transform->setMatrix(mOffset * worldReference * osg::Matrix::inverse(worldToLimb) * transform->getMatrix());
|
|
||||||
|
|
||||||
|
// 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.
|
Log(Debug::Verbose) << "Updating hand: " << node->getName();
|
||||||
// But I want to disable idle animations.
|
|
||||||
|
// Omit nested callbacks to override animations of this node
|
||||||
|
osg::ref_ptr<osg::Callback> ncb = getNestedCallback();
|
||||||
|
setNestedCallback(nullptr);
|
||||||
traverse(node, nv);
|
traverse(node, nv);
|
||||||
|
setNestedCallback(ncb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements control of a finger by overriding rotation
|
/// Implements control of a finger by overriding rotation
|
||||||
class FingerController : public osg::NodeCallback
|
class FingerController : public osg::NodeCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FingerController(osg::Quat rotate) : mRotate(rotate) {};
|
FingerController() {};
|
||||||
void setEnabled(bool enabled) { mEnabled = enabled; };
|
void setEnabled(bool enabled) { mEnabled = enabled; };
|
||||||
void operator()(osg::Node* node, osg::NodeVisitor* nv);
|
void operator()(osg::Node* node, osg::NodeVisitor* nv);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool mEnabled = true;
|
bool mEnabled = true;
|
||||||
osg::Quat mRotate{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void FingerController::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
void FingerController::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||||
|
@ -153,36 +208,224 @@ void FingerController::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||||
return;
|
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
|
// First, update the base of the finger to the overriding orientation
|
||||||
auto matrixTransform = node->asTransform()->asMatrixTransform();
|
auto matrixTransform = node->asTransform()->asMatrixTransform();
|
||||||
auto matrix = matrixTransform->getMatrix();
|
auto matrix = matrixTransform->getMatrix();
|
||||||
matrix.setRotate(mRotate);
|
matrix.setRotate(rotate);
|
||||||
matrixTransform->setMatrix(matrix);
|
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
|
// Omit nested callbacks to override animations of this node
|
||||||
// So that we can display a beam to visualize where the player is pointing.
|
osg::ref_ptr<osg::Callback> 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();
|
auto* anim = MWVR::Environment::get().getPlayerAnimation();
|
||||||
if (anim)
|
if (anim && node->getName() == "Bip01 R Finger1")
|
||||||
|
{
|
||||||
anim->updatePointerTarget();
|
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<osg::Callback> 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<osg::MatrixTransform*>(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(
|
VRAnimation::VRAnimation(
|
||||||
const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> parentNode, Resource::ResourceSystem* resourceSystem,
|
const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> 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.
|
// 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))))
|
, mModelOffset(new osg::MatrixTransform(osg::Matrix::translate(osg::Vec3(0,-15,0))))
|
||||||
{
|
{
|
||||||
mIndexFingerControllers[0] = osg::ref_ptr<FingerController> (new FingerController(osg::Quat(0, 0, 0, 1)));
|
for (int i = 0; i < 2; i++)
|
||||||
mIndexFingerControllers[1] = osg::ref_ptr<FingerController> (new FingerController(osg::Quat(0, 0, 0, 1)));
|
{
|
||||||
|
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");
|
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() {};
|
VRAnimation::~VRAnimation() {};
|
||||||
|
@ -236,58 +504,29 @@ void VRAnimation::updateParts()
|
||||||
removeIndividualPart(ESM::PartReferenceType::PRT_LAnkle);
|
removeIndividualPart(ESM::PartReferenceType::PRT_LAnkle);
|
||||||
removeIndividualPart(ESM::PartReferenceType::PRT_RAnkle);
|
removeIndividualPart(ESM::PartReferenceType::PRT_RAnkle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VRAnimation::setPointForward(bool enabled)
|
void VRAnimation::setPointForward(bool enabled)
|
||||||
{
|
{
|
||||||
auto found00 = mNodeMap.find("bip01 r finger1");
|
auto finger = mNodeMap.find("bip01 r finger1");
|
||||||
//auto weapon = mNodeMap.find("weapon");
|
if (finger != mNodeMap.end())
|
||||||
if (found00 != mNodeMap.end())
|
|
||||||
{
|
{
|
||||||
auto base_joint = found00->second;
|
auto base_joint = finger->second;
|
||||||
auto second_joint = base_joint->getChild(0)->asTransform()->asMatrixTransform();
|
auto second_joint = base_joint->getChild(0)->asTransform()->asMatrixTransform();
|
||||||
assert(second_joint);
|
assert(second_joint);
|
||||||
|
|
||||||
second_joint->removeChild(mPointerTransform);
|
|
||||||
//weapon->second->removeChild(mWeaponDirectionTransform);
|
|
||||||
mWeaponDirectionTransform->removeChild(mPointerGeometry);
|
|
||||||
base_joint->removeUpdateCallback(mIndexFingerControllers[0]);
|
base_joint->removeUpdateCallback(mIndexFingerControllers[0]);
|
||||||
|
second_joint->removeUpdateCallback(mIndexFingerControllers[1]);
|
||||||
if (enabled)
|
if (enabled)
|
||||||
{
|
{
|
||||||
second_joint->addChild(mPointerTransform);
|
|
||||||
//weapon->second->addChild(mWeaponDirectionTransform);
|
|
||||||
mWeaponDirectionTransform->addChild(mPointerGeometry);
|
|
||||||
base_joint->addUpdateCallback(mIndexFingerControllers[0]);
|
base_joint->addUpdateCallback(mIndexFingerControllers[0]);
|
||||||
}
|
second_joint->addUpdateCallback(mIndexFingerControllers[1]);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VRAnimation::createPointer(void)
|
mPointerTransform->removeChild(mPointerRescale);
|
||||||
{
|
if (enabled)
|
||||||
mPointerGeometry = createPointerGeometry();
|
mPointerTransform->addChild(mPointerRescale);
|
||||||
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");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::ref_ptr<osg::Geometry> VRAnimation::createPointerGeometry(void)
|
osg::ref_ptr<osg::Geometry> VRAnimation::createPointerGeometry(void)
|
||||||
|
@ -300,8 +539,8 @@ osg::ref_ptr<osg::Geometry> VRAnimation::createPointerGeometry(void)
|
||||||
osg::Vec3 vertices[]{
|
osg::Vec3 vertices[]{
|
||||||
{0, 0, 0}, // origin
|
{0, 0, 0}, // origin
|
||||||
{1, 1, -1}, // top_left
|
{1, 1, -1}, // top_left
|
||||||
{1, -1, -1}, // bottom_left
|
{-1, 1, -1}, // bottom_left
|
||||||
{1, -1, 1}, // bottom_right
|
{-1, 1, 1}, // bottom_right
|
||||||
{1, 1, 1}, // top_right
|
{1, 1, 1}, // top_right
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -340,8 +579,6 @@ osg::ref_ptr<osg::Geometry> VRAnimation::createPointerGeometry(void)
|
||||||
osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
|
osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
|
||||||
normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));
|
normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
geometry->setVertexArray(vertexArray);
|
geometry->setVertexArray(vertexArray);
|
||||||
geometry->setColorArray(colorArray, osg::Array::BIND_PER_VERTEX);
|
geometry->setColorArray(colorArray, osg::Array::BIND_PER_VERTEX);
|
||||||
geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, numVertices));
|
geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, numVertices));
|
||||||
|
@ -367,58 +604,46 @@ void VRAnimation::addControllers()
|
||||||
{
|
{
|
||||||
NpcAnimation::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)
|
for (int i = 0; i < 2; ++i)
|
||||||
{
|
{
|
||||||
mHandControllers[i] = nullptr;
|
auto forearm = mNodeMap.find(i == 0 ? "bip01 l forearm" : "bip01 r forearm");
|
||||||
|
if (forearm != mNodeMap.end())
|
||||||
SceneUtil::FindByNameVisitor findTrackerVisitor(i == 0 ? "tracker l hand" : "tracker r hand");
|
|
||||||
xrRoot->accept(findTrackerVisitor);
|
|
||||||
if (!findTrackerVisitor.mFoundNode)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
SceneUtil::PositionAttitudeTransform* tracker = dynamic_cast<SceneUtil::PositionAttitudeTransform*>(findTrackerVisitor.mFoundNode);
|
|
||||||
|
|
||||||
auto found = mNodeMap.find(i == 0 ? "bip01 l forearm" : "bip01 r forearm");
|
|
||||||
if (found != mNodeMap.end())
|
|
||||||
{
|
{
|
||||||
osg::Node* node = found->second;
|
auto node = forearm->second;
|
||||||
mForearmControllers[i] = new ForearmController(mObjectRoot, tracker);
|
node->removeUpdateCallback(mForearmControllers[i]);
|
||||||
node->addUpdateCallback(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);
|
auto parent = mObjectRoot->getParent(0);
|
||||||
|
|
||||||
if (parent->getName() == "Player Root")
|
if (parent->getName() == "Player Root")
|
||||||
{
|
{
|
||||||
auto group = parent->asGroup();
|
auto group = parent->asGroup();
|
||||||
group->removeChildren(0, parent->getNumChildren());
|
group->removeChildren(0, parent->getNumChildren());
|
||||||
group->addChild(mModelOffset);
|
group->addChild(mModelOffset);
|
||||||
mModelOffset->addChild(mObjectRoot);
|
mModelOffset->addChild(mObjectRoot);
|
||||||
|
|
||||||
auto weapon = mNodeMap.find("weapon");
|
|
||||||
weapon->second->addChild(mWeaponDirectionTransform);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
void VRAnimation::enableHeadAnimation(bool)
|
void VRAnimation::enableHeadAnimation(bool)
|
||||||
{
|
{
|
||||||
|
@ -454,13 +679,13 @@ void VRAnimation::updatePointerTarget()
|
||||||
auto* world = MWBase::Environment::get().getWorld();
|
auto* world = MWBase::Environment::get().getWorld();
|
||||||
if (world)
|
if (world)
|
||||||
{
|
{
|
||||||
mPointerTransform->setMatrix(osg::Matrix::scale(1, 1, 1));
|
mPointerRescale->setMatrix(osg::Matrix::scale(1, 1, 1));
|
||||||
mDistanceToPointerTarget = world->getTargetObject(mPointerTarget, mPointerTransform);
|
mDistanceToPointerTarget = world->getTargetObject(mPointerTarget, mPointerTransform);
|
||||||
|
|
||||||
if(mDistanceToPointerTarget >= 0)
|
if(mDistanceToPointerTarget >= 0)
|
||||||
mPointerTransform->setMatrix(osg::Matrix::scale(mDistanceToPointerTarget, 0.25f, 0.25f));
|
mPointerRescale->setMatrix(osg::Matrix::scale(0.25f, mDistanceToPointerTarget, 0.25f));
|
||||||
else
|
else
|
||||||
mPointerTransform->setMatrix(osg::Matrix::scale(10000.f, 0.25f, 0.25f));
|
mPointerRescale->setMatrix(osg::Matrix::scale(0.25f, 10000.f, 0.25f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,18 +65,17 @@ public:
|
||||||
void updatePointerTarget();
|
void updatePointerTarget();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void createPointer(void);
|
|
||||||
static osg::ref_ptr<osg::Geometry> createPointerGeometry(void);
|
static osg::ref_ptr<osg::Geometry> createPointerGeometry(void);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::shared_ptr<OpenXRSession> mSession;
|
std::shared_ptr<OpenXRSession> mSession;
|
||||||
ForearmController* mForearmControllers[2]{};
|
osg::ref_ptr<ForearmController> mForearmControllers[2];
|
||||||
HandController* mHandControllers[2]{};
|
osg::ref_ptr<HandController> mHandControllers[2];
|
||||||
osg::ref_ptr<FingerController> mIndexFingerControllers[2];
|
osg::ref_ptr<FingerController> mIndexFingerControllers[2];
|
||||||
osg::ref_ptr<osg::MatrixTransform> mModelOffset;
|
osg::ref_ptr<osg::MatrixTransform> mModelOffset;
|
||||||
osg::ref_ptr<osg::Geometry> mPointerGeometry{ nullptr };
|
osg::ref_ptr<osg::Geometry> mPointerGeometry{ nullptr };
|
||||||
|
osg::ref_ptr<osg::MatrixTransform> mPointerRescale{ nullptr };
|
||||||
osg::ref_ptr<osg::MatrixTransform> mPointerTransform{ nullptr };
|
osg::ref_ptr<osg::MatrixTransform> mPointerTransform{ nullptr };
|
||||||
osg::ref_ptr<osg::MatrixTransform> mWeaponAdjustment{ nullptr };
|
|
||||||
osg::ref_ptr<osg::MatrixTransform> mWeaponDirectionTransform{ nullptr };
|
osg::ref_ptr<osg::MatrixTransform> mWeaponDirectionTransform{ nullptr };
|
||||||
osg::ref_ptr<osg::MatrixTransform> mWeaponPointerTransform{ nullptr };
|
osg::ref_ptr<osg::MatrixTransform> mWeaponPointerTransform{ nullptr };
|
||||||
MWRender::RayResult mPointerTarget{};
|
MWRender::RayResult mPointerTarget{};
|
||||||
|
|
|
@ -32,9 +32,6 @@ void MWVR::Environment::cleanup()
|
||||||
if (mMenuManager)
|
if (mMenuManager)
|
||||||
delete mMenuManager;
|
delete mMenuManager;
|
||||||
mMenuManager = nullptr;
|
mMenuManager = nullptr;
|
||||||
if (mPlayerAnimation)
|
|
||||||
delete mPlayerAnimation;
|
|
||||||
mPlayerAnimation = nullptr;
|
|
||||||
if (mViewer)
|
if (mViewer)
|
||||||
delete mViewer;
|
delete mViewer;
|
||||||
mViewer = nullptr;
|
mViewer = nullptr;
|
||||||
|
|
|
@ -43,6 +43,11 @@
|
||||||
|
|
||||||
#include "../mwphysics/physicssystem.hpp"
|
#include "../mwphysics/physicssystem.hpp"
|
||||||
|
|
||||||
|
#ifdef USE_OPENXR
|
||||||
|
#include "../mwvr/vrenvironment.hpp"
|
||||||
|
#include "../mwvr/vranimation.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
ESM::EffectList getMagicBoltData(std::vector<std::string>& projectileIDs, std::set<std::string>& sounds, float& speed, std::string& texture, std::string& sourceName, const std::string& id)
|
ESM::EffectList getMagicBoltData(std::vector<std::string>& projectileIDs, std::set<std::string>& sounds, float& speed, std::string& texture, std::string& sourceName, const std::string& id)
|
||||||
|
@ -269,6 +274,16 @@ namespace MWWorld
|
||||||
return;
|
return;
|
||||||
|
|
||||||
osg::Quat orient;
|
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())
|
if (caster.getClass().isActor())
|
||||||
orient = osg::Quat(caster.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
|
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));
|
* osg::Quat(caster.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
|
||||||
|
|
|
@ -1154,7 +1154,7 @@ namespace MWWorld
|
||||||
if (ptr == getPlayerPtr())
|
if (ptr == getPlayerPtr())
|
||||||
{
|
{
|
||||||
#ifdef USE_OPENXR
|
#ifdef USE_OPENXR
|
||||||
// TODO: Configurable realistic fighting.
|
// TODO: Configurable realistic fighting ?
|
||||||
|
|
||||||
// Use current aim of weapon to impact
|
// Use current aim of weapon to impact
|
||||||
// TODO: Use bounding box of weapon instead ?
|
// TODO: Use bounding box of weapon instead ?
|
||||||
|
@ -3063,8 +3063,11 @@ namespace MWWorld
|
||||||
|
|
||||||
// for player we can take faced object first
|
// for player we can take faced object first
|
||||||
MWWorld::Ptr target;
|
MWWorld::Ptr target;
|
||||||
|
#ifndef USE_OPENXR
|
||||||
|
// Does not apply to VR
|
||||||
if (actor == MWMechanics::getPlayer())
|
if (actor == MWMechanics::getPlayer())
|
||||||
target = getFacedObject();
|
target = getFacedObject();
|
||||||
|
#endif
|
||||||
|
|
||||||
// if the faced object can not be activated, do not use it
|
// if the faced object can not be activated, do not use it
|
||||||
if (!target.isEmpty() && !target.getClass().hasToolTip(target))
|
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 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));
|
* 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);
|
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
|
||||||
float distance = getMaxActivationDistance();
|
float distance = getMaxActivationDistance();
|
||||||
osg::Vec3f dest = origin + direction * distance;
|
osg::Vec3f dest = origin + direction * distance;
|
||||||
|
|
||||||
|
|
||||||
MWRender::RayResult result2 = mRendering->castRay(origin, dest, true, true);
|
MWRender::RayResult result2 = mRendering->castRay(origin, dest, true, true);
|
||||||
|
|
||||||
float dist1 = std::numeric_limits<float>::max();
|
float dist1 = std::numeric_limits<float>::max();
|
||||||
|
@ -4000,4 +4014,35 @@ namespace MWWorld
|
||||||
{
|
{
|
||||||
return mPhysics.get();
|
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<ESM::Weapon>()->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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -745,6 +745,11 @@ namespace MWWorld
|
||||||
|
|
||||||
|
|
||||||
MWPhysics::PhysicsSystem* getPhysicsSystem(void) override;
|
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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue