Improvements on combat

pull/615/head
Mads Buvik Sandvei 5 years ago
parent 893b75d767
commit 68c75f66eb

@ -240,7 +240,7 @@ if (WIN32)
# Get rid of useless crud from windows.h
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
# Get rid of the nonsense warning "enum type is unscoped"
# Get rid of the pointless warning "enum type is unscoped"
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd26812")
endif()

@ -647,7 +647,12 @@ namespace MWBase
virtual float getTargetObject(MWRender::RayResult& result, osg::Transform* pointer) = 0;
virtual float getTargetObject(MWRender::RayResult& result, osg::Transform* pointer, float maxDistance, bool ignorePlayer) = 0;
/// @Return ESM::Weapon::Type enum describing the type of weapon currently drawn by the player.
virtual int getActiveWeaponType(void) = 0;
virtual MWPhysics::PhysicsSystem* getPhysicsSystem(void) = 0;
virtual void toggleWaterRTT(bool enable) = 0;
};
}

@ -48,10 +48,6 @@
#include "actorutil.hpp"
#include "spellcasting.hpp"
#ifdef USE_OPENXR
#include "../mwvr/vranimation.hpp"
#endif
namespace
{

@ -454,7 +454,6 @@ namespace MWPhysics
osg::Vec3 moved = newPosition - position;
inputManager->mHeadOffset.x() -= moved.x();
inputManager->mHeadOffset.y() -= moved.y();
}
#endif

@ -43,6 +43,7 @@
#include "../mwworld/cellstore.hpp"
#include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority
#include "../mwmechanics/actorutil.hpp"
#include "vismask.hpp"
#include "util.hpp"
@ -895,6 +896,13 @@ namespace MWRender
return;
}
const bool isPlayer = (mPtr == MWMechanics::getPlayer());
if (isPlayer)
{
Log(Debug::Verbose) << "groupname=" << groupname << ", start=" << start << ", stop=" << stop << ", accumRoot=" << mAccumRoot->getName();
}
AnimStateMap::iterator stateiter = mStates.begin();
while(stateiter != mStates.end())
{
@ -1053,8 +1061,65 @@ namespace MWRender
return mNodeMap;
}
// In VR, only jump, walk, and run groups should accumulate movement.
static bool vrAccum(const std::string& groupname)
{
#ifdef USE_OPENXR
if (groupname.compare(0, 4, "jump"))
if (groupname.compare(0, 4, "walk"))
if (groupname.compare(0, 3, "run"))
return false;
#else
(void)groupname;
#endif
return true;
}
static bool vrOverride(const std::string& groupname, const std::string& bone)
{
#ifdef USE_OPENXR
// TODO: Some overrides cause NaN during cull.
// I believe this happens if an override causes a bone to never receive
// a valid matrix, but i'm not totally sure.
// Add any bone+animation pair that is messing with Vr comfort here.
using Overrides = std::set<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()
{
const bool isPlayer = (mPtr == MWMechanics::getPlayer());
// remove all previous external controllers from the scene graph
for (ControllerMap::iterator it = mActiveControllers.begin(); it != mActiveControllers.end(); ++it)
{
@ -1093,11 +1158,12 @@ namespace MWRender
for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it)
{
osg::ref_ptr<osg::Node> node = getNodeMap().at(it->first); // this should not throw, we already checked for the node existing in addAnimSource
node->addUpdateCallback(it->second);
if(!isPlayer || !vrOverride(active->first, it->first))
node->addUpdateCallback(it->second);
mActiveControllers.insert(std::make_pair(node, it->second));
if (blendMask == 0 && node == mAccumRoot)
if (blendMask == 0 && node == mAccumRoot
&& (!isPlayer || vrAccum(active->first)))
{
mAccumCtrl = it->second;

@ -163,10 +163,6 @@ namespace MWRender
setYaw(yaw);
setPitch(pitch);
setRoll(roll);
// This might happen mid-update traversal because of openxr input management.
// It is essential to VR comfort that this be effective immediately and not next frame.
updateCamera();
}
void Camera::attachTo(const MWWorld::Ptr &ptr)
@ -431,7 +427,7 @@ namespace MWRender
mTrackingNode = findRootVisitor.mFoundNode;
if (!mTrackingNode)
throw std::logic_error("Unapple to find tracking node for VR camera");
throw std::logic_error("Unable to find tracking node for VR camera");
mHeightScale = 1.f;
#else
if(isFirstPerson())
@ -452,7 +448,7 @@ namespace MWRender
else
mHeightScale = 1.f;
}
rotateCamera(getPitch(), getYaw(), false);
rotateCamera(getPitch(), 0.f, getYaw(), false);
#endif
}

@ -36,9 +36,7 @@ public:
VM_Normal,
VM_FirstPerson,
VM_HeadOnly,
#ifdef USE_OPENXR
VM_VRHeadless,
#endif
};
protected:

@ -1093,7 +1093,7 @@ namespace MWRender
{
osg::Matrix worldMatrix = osg::computeLocalToWorld(source->getParentalNodePaths()[0]);
osg::Vec3f direction = worldMatrix.getRotate() * osg::Vec3f(1, 0, 0);
osg::Vec3f direction = worldMatrix.getRotate() * osg::Vec3f(0, 1, 0);
direction.normalize();
osg::Vec3f raySource = worldMatrix.getTrans();
@ -1469,6 +1469,11 @@ namespace MWRender
mNavMeshNumber = value;
}
void RenderingManager::toggleWaterRTT(bool enable)
{
mWater->toggleRTT(enable);
}
void RenderingManager::updateNavMesh()
{
if (!mNavMesh->isEnabled())

@ -242,6 +242,8 @@ namespace MWRender
void setNavMeshNumber(const std::size_t value);
void toggleWaterRTT(bool enable);
private:
void updateProjectionMatrix();
void updateTextureFiltering();

@ -436,6 +436,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem
, mToggled(true)
, mTop(0)
, mInterior(false)
, mRTTToggled(true)
{
mSimulation.reset(new RippleSimulation(mSceneRoot, resourceSystem));
@ -720,6 +721,20 @@ bool Water::toggle()
return mToggled;
}
void Water::toggleRTT(bool enable)
{
mRTTToggled = enable;
bool visible = mEnabled && mToggled && mRTTToggled;
// The idea here is to stop RTT from happening on one eye to save performance
// This didn't work, though
//{
// if (mRefraction)
// mRefraction->setNodeMask(visible ? Mask_RenderToTexture : 0);
// if (mReflection)
// mReflection->setNodeMask(visible ? Mask_RenderToTexture : 0);
//}
}
bool Water::isUnderwater(const osg::Vec3f &pos) const
{
return pos.z() < mTop && mToggled && mEnabled;

@ -71,6 +71,8 @@ namespace MWRender
float mTop;
bool mInterior;
bool mRTTToggled;
osg::Vec3f getSceneNodeCoordinates(int gridX, int gridY);
void updateVisible();
@ -94,6 +96,9 @@ namespace MWRender
bool toggle();
/// Call before each eye to allow rendering water only once per frame in VR
void toggleRTT(bool enable);
bool isUnderwater(const osg::Vec3f& pos) const;
/// adds an emitter, position will be tracked automatically using its scene node

@ -17,6 +17,11 @@
#include "../mwmechanics/combat.hpp"
#include "../mwmechanics/weapontype.hpp"
#ifdef USE_OPENXR
#include "../mwvr/vrenvironment.hpp"
#include "../mwvr/vranimation.hpp"
#endif
#include "animation.hpp"
#include "rotatecontroller.hpp"
@ -107,9 +112,16 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength)
if (weapon->getTypeName() != typeid(ESM::Weapon).name())
return;
#ifdef USE_OPENXR
// In VR player rotation and weapon aim are unrelated.
auto* anim = MWVR::Environment::get().getPlayerAnimation();
osg::Matrix worldMatrix = osg::computeLocalToWorld(anim->mWeaponDirectionTransform->getParentalNodePaths()[0]);
osg::Quat orient = worldMatrix.getRotate();
#else
// The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise.
osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
* osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
#endif
const MWWorld::Store<ESM::GameSetting> &gmst =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();

@ -760,11 +760,16 @@ void OpenXRInputManager::updateActivationIndication(void)
bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode();
bool show = guiMode | mActivationIndication;
if (mPlayer)
mPlayer->setPointing(show);
{
if (show != mPlayer->getPointing())
{
mPlayer->setPointing(show);
auto* playerAnimation = Environment::get().getPlayerAnimation();
if (playerAnimation)
playerAnimation->setPointForward(show);
auto* playerAnimation = Environment::get().getPlayerAnimation();
if (playerAnimation)
playerAnimation->setPointForward(show);
}
}
}
@ -938,8 +943,11 @@ private:
auto player = mPlayer->getPlayer();
if (!mRealisticCombat || mRealisticCombat->ptr != player)
mRealisticCombat.reset(new RealisticCombat::StateMachine(player));
mRealisticCombat->update(dt, !guiMode);
bool enabled = !guiMode && mPlayer->getDrawState() == MWMechanics::DrawState_Weapon;
mRealisticCombat->update(dt, enabled);
}
updateHead();
}
void OpenXRInputManager::processEvent(const OpenXRActionEvent& event)
@ -1112,7 +1120,7 @@ private:
auto* xr = Environment::get().getManager();
auto* session = Environment::get().getSession();
auto currentHeadPose = session->predictedPoses().head[(int)TrackedSpace::STAGE];
auto currentHeadPose = session->predictedPoses(OpenXRSession::PredictionSlice::Predraw).head[(int)TrackedSpace::STAGE];
xr->playerScale(currentHeadPose);
currentHeadPose.position *= Environment::get().unitsPerMeter();
osg::Vec3 vrMovement = currentHeadPose.position - mPreviousHeadPose.position;
@ -1141,8 +1149,6 @@ private:
mVrAngles[1] = roll;
mVrAngles[2] = yaw;
world->rotateObject(player, mVrAngles[0], mVrAngles[1], mVrAngles[2], MWBase::RotationFlag_none);
MWBase::Environment::get().getWorld()->getRenderingManager().getCamera()->updateCamera();
}
}

@ -48,34 +48,49 @@ namespace MWVR
auto* xr = Environment::get().getManager();
if (!xr->sessionRunning())
if (!mShouldRender)
return;
if (!mPredictionsReady)
if (mRenderedFrames != mRenderFrame - 1)
{
Log(Debug::Warning) << "swapBuffers called out of order";
waitFrame();
return;
}
for (auto layer : mLayerStack.layerObjects())
if(layer)
layer->swapBuffers(gc);
timer.checkpoint("Rendered");
xr->endFrame(xr->impl().frameState().predictedDisplayTime, &mLayerStack);
mRenderedFrames++;
}
void OpenXRSession::waitFrame()
const PoseSets& OpenXRSession::predictedPoses(PredictionSlice slice)
{
auto* xr = Environment::get().getManager();
xr->handleEvents();
if (!xr->sessionRunning())
return;
Timer timer("OpenXRSession::waitFrame");
xr->waitFrame();
timer.checkpoint("waitFrame");
predictNext(0);
mPredictionsReady = true;
if(slice == PredictionSlice::Predraw)
waitFrame();
return mPredictedPoses[(int)slice];
}
void OpenXRSession::waitFrame()
{
if(mPredictedFrames < mPredictionFrame)
{
Timer timer("OpenXRSession::waitFrame");
auto* xr = Environment::get().getManager();
xr->handleEvents();
mIsRunning = xr->sessionRunning();
if (!mIsRunning)
return;
xr->waitFrame();
timer.checkpoint("waitFrame");
predictNext(0);
mPredictedFrames++;
}
}
@ -118,7 +133,7 @@ namespace MWVR
float OpenXRSession::movementYaw(void)
{
auto lhandquat = predictedPoses().hands[(int)TrackedSpace::VIEW][(int)MWVR::Side::LEFT_HAND].orientation;
auto lhandquat = predictedPoses(PredictionSlice::Predraw).hands[(int)TrackedSpace::VIEW][(int)MWVR::Side::LEFT_HAND].orientation;
float yaw = 0.f;
float pitch = 0.f;
float roll = 0.f;
@ -126,25 +141,42 @@ namespace MWVR
return yaw;
}
void OpenXRSession::advanceFrame(void)
{
auto* xr = Environment::get().getManager();
mShouldRender = mIsRunning;
if (!mIsRunning)
return;
if (mPredictedFrames != mPredictionFrame)
{
Log(Debug::Warning) << "advanceFrame() called out of order";
return;
}
xr->beginFrame();
mPredictionFrame++;
mRenderFrame++;
mPredictedPoses[(int)PredictionSlice::Draw] = mPredictedPoses[(int)PredictionSlice::Predraw];
}
void OpenXRSession::predictNext(int extraPeriods)
{
auto* xr = Environment::get().getManager();
auto* input = Environment::get().getInputManager();
auto mPredictedDisplayTime = xr->impl().frameState().predictedDisplayTime;
auto previousHeadPose = mPredictedPoses.head[(int)TrackedSpace::STAGE];
// Update pose predictions
mPredictedPoses.head[(int)TrackedSpace::STAGE] = xr->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::STAGE);
mPredictedPoses.head[(int)TrackedSpace::VIEW] = xr->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::VIEW);
mPredictedPoses.hands[(int)TrackedSpace::STAGE] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::STAGE);
mPredictedPoses.hands[(int)TrackedSpace::VIEW] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::VIEW);
PoseSets newPoses{};
newPoses.head[(int)TrackedSpace::STAGE] = xr->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::STAGE);
newPoses.head[(int)TrackedSpace::VIEW] = xr->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::VIEW);
newPoses.hands[(int)TrackedSpace::STAGE] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::STAGE);
newPoses.hands[(int)TrackedSpace::VIEW] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::VIEW);
auto stageViews = xr->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::STAGE);
auto hmdViews = xr->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::VIEW);
mPredictedPoses.eye[(int)TrackedSpace::STAGE][(int)Side::LEFT_HAND] = fromXR(stageViews[(int)Side::LEFT_HAND].pose);
mPredictedPoses.eye[(int)TrackedSpace::VIEW][(int)Side::LEFT_HAND] = fromXR(hmdViews[(int)Side::LEFT_HAND].pose);
mPredictedPoses.eye[(int)TrackedSpace::STAGE][(int)Side::RIGHT_HAND] = fromXR(stageViews[(int)Side::RIGHT_HAND].pose);
mPredictedPoses.eye[(int)TrackedSpace::VIEW][(int)Side::RIGHT_HAND] = fromXR(hmdViews[(int)Side::RIGHT_HAND].pose);
newPoses.eye[(int)TrackedSpace::STAGE][(int)Side::LEFT_HAND] = fromXR(stageViews[(int)Side::LEFT_HAND].pose);
newPoses.eye[(int)TrackedSpace::VIEW][(int)Side::LEFT_HAND] = fromXR(hmdViews[(int)Side::LEFT_HAND].pose);
newPoses.eye[(int)TrackedSpace::STAGE][(int)Side::RIGHT_HAND] = fromXR(stageViews[(int)Side::RIGHT_HAND].pose);
newPoses.eye[(int)TrackedSpace::VIEW][(int)Side::RIGHT_HAND] = fromXR(hmdViews[(int)Side::RIGHT_HAND].pose);
mPredictedPoses[(int)PredictionSlice::Predraw] = newPoses;
}
}

@ -19,11 +19,19 @@ extern void getEulerAngles(const osg::Quat& quat, float& yaw, float& pitch, floa
class OpenXRSession
{
public:
using seconds = std::chrono::duration<double>;
using nanoseconds = std::chrono::nanoseconds;
using clock = std::chrono::steady_clock;
using time_point = clock::time_point;
enum class PredictionSlice
{
Predraw = 0, //!< Get poses predicted for the next frame to be drawn
Draw = 1, //!< Get poses predicted for the current rendering
NumSlices
};
public:
OpenXRSession();
~OpenXRSession();
@ -31,7 +39,7 @@ public:
void setLayer(OpenXRLayerStack::Layer layerType, OpenXRLayer* layer);
void swapBuffers(osg::GraphicsContext* gc);
const PoseSets& predictedPoses() const { return mPredictedPoses; };
const PoseSets& predictedPoses(PredictionSlice slice);
//! Call before updating poses and other inputs
void waitFrame();
@ -42,10 +50,22 @@ public:
//! Yaw angle to be used for offsetting movement direction
float movementYaw(void);
void advanceFrame(void);
bool isRunning() { return mIsRunning; }
bool shouldRender() { return mShouldRender; }
OpenXRLayerStack mLayerStack{};
PoseSets mPredictedPoses{};
bool mPredictionsReady{ false };
PoseSets mPredictedPoses[(int)PredictionSlice::NumSlices]{};
int mRenderFrame{ 0 };
int mRenderedFrames{ 0 };
int mPredictionFrame{ 1 };
int mPredictedFrames{ 0 };
bool mIsRunning{ false };
bool mShouldRender{ false };
};
}

@ -125,10 +125,6 @@ namespace MWVR {
Timer timer("Swapchain::endFrame");
// Blit frame to swapchain
if (!mXR->sessionRunning())
return -1;
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
uint32_t swapchainImageIndex = 0;
CHECK_XRCMD(xrAcquireSwapchainImage(mSwapchain, &acquireInfo, &swapchainImageIndex));

@ -2,6 +2,11 @@
#include "openxrmanager.hpp"
#include "openxrmanagerimpl.hpp"
#include "../mwinput/inputmanagerimp.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwrender/renderingmanager.hpp"
#include "../mwrender/water.hpp"
#include <components/debug/debuglog.hpp>
#include <components/sdlutil/sdlgraphicswindow.hpp>
@ -46,6 +51,7 @@ namespace MWVR {
camera->setGraphicsContext(gc);
camera->setInitialDrawCallback(new OpenXRView::InitialDrawCallback());
camera->setFinalDrawCallback(new OpenXRView::FinalDrawCallback());
return camera.release();
}
@ -57,13 +63,18 @@ namespace MWVR {
mSwapchain->beginFrame(renderInfo.getState()->getGraphicsContext());
}
mTimer.checkpoint("Prerender");
}
void OpenXRView::postrenderCallback(osg::RenderInfo& renderInfo)
{
auto name = renderInfo.getCurrentCamera()->getName();
mTimer.checkpoint("Postrender");
auto state = renderInfo.getState();
auto gl = osg::GLExtensions::Get(state->getContextID(), false);
// Water RTT happens before the initial draw callback,
// so i have to disable it in postrender of the first eye, and then re-enable it
auto world = MWBase::Environment::get().getWorld();
if (world)
world->toggleWaterRTT(name == "RightEye");
}
void OpenXRView::swapBuffers(osg::GraphicsContext* gc)

@ -19,6 +19,11 @@ namespace MWVR
public:
virtual void operator()(osg::RenderInfo& renderInfo) const;
};
class FinalDrawCallback : public osg::Camera::DrawCallback
{
public:
virtual void operator()(osg::RenderInfo& renderInfo) const;
};
protected:
OpenXRView(osg::ref_ptr<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[1].pose.orientation.w = 1;
this->setName("OpenXRRoot");
this->setUserData(new TrackedNodeUpdateCallback(this));
mLeftHandTransform->setName("tracker l hand");
mLeftHandTransform->setUpdateCallback(new TrackedNodeUpdateCallback(this));
this->addChild(mLeftHandTransform);
mRightHandTransform->setName("tracker r hand");
mRightHandTransform->setUpdateCallback(new TrackedNodeUpdateCallback(this));
this->addChild(mRightHandTransform);
}
OpenXRViewer::~OpenXRViewer(void)
@ -64,8 +54,6 @@ namespace MWVR
void OpenXRViewer::traversals()
{
auto* xr = Environment::get().getManager();
xr->handleEvents();
mViewer->updateTraversal();
mViewer->renderingTraversals();
}
@ -256,8 +244,10 @@ namespace MWVR
rightView->swapBuffers(gc);
timer.checkpoint("Views");
mCompositionLayerProjectionViews[0].pose = toXR(session->predictedPoses().eye[(int)TrackedSpace::STAGE][(int)Side::LEFT_HAND]);
mCompositionLayerProjectionViews[1].pose = toXR(session->predictedPoses().eye[(int)TrackedSpace::STAGE][(int)Side::RIGHT_HAND]);
auto& drawPoses = session->predictedPoses(OpenXRSession::PredictionSlice::Draw);
mCompositionLayerProjectionViews[0].pose = toXR(drawPoses.eye[(int)TrackedSpace::STAGE][(int)Side::LEFT_HAND]);
mCompositionLayerProjectionViews[1].pose = toXR(drawPoses.eye[(int)TrackedSpace::STAGE][(int)Side::RIGHT_HAND]);
timer.checkpoint("Poses");
@ -304,13 +294,7 @@ namespace MWVR
auto& view = mViews[name];
if (name == "LeftEye")
{
auto* xr = Environment::get().getManager();
if (xr->sessionRunning())
{
xr->beginFrame();
}
}
Environment::get().getSession()->advanceFrame();
view->prerenderCallback(info);
}
@ -330,86 +314,4 @@ namespace MWVR
Log(Debug::Warning) << ("osg overwrote predraw");
}
}
void OpenXRViewer::updateTransformNode(osg::Object* object, osg::Object* data)
{
auto* hand_transform = dynamic_cast<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;
};
class TrackedNodeUpdateCallback : public osg::Callback
{
public:
TrackedNodeUpdateCallback(OpenXRViewer* viewer) : mViewer(viewer) {};
private:
virtual bool run(osg::Object* object, osg::Object* data);
OpenXRViewer* mViewer;
};
public:
OpenXRViewer(
osg::ref_ptr<osgViewer::Viewer> viewer);
@ -111,10 +100,6 @@ namespace MWVR
osg::ref_ptr<osgViewer::Viewer> mViewer = nullptr;
std::map<std::string, osg::ref_ptr<OpenXRView> > mViews{};
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 };
PostdrawCallback* mPostDraw{ nullptr };

@ -143,6 +143,12 @@ namespace MWVR
// Disable normal OSG FBO camera setup because it will undo the MSAA FBO configuration.
renderer->setCameraRequiresSetUp(false);
}
auto name = renderInfo.getCurrentCamera()->getName();
}
// TODO: This would have been useful but OSG never calls it.
void OpenXRWorldView::FinalDrawCallback::operator()(osg::RenderInfo& renderInfo) const
{
}
void
@ -151,37 +157,27 @@ namespace MWVR
osg::View::Slave& slave)
{
mView->mTimer.checkpoint("UpdateSlave");
auto* camera = slave._camera.get();
auto name = camera->getName();
auto& poses = mSession->predictedPoses();
Pose viewPose{};
Pose stagePose{};
int side = 0;
if (name == "LeftEye")
{
mSession->waitFrame();
// Updating the head pose needs to happen after waitFrame(),
// But i can't call waitFrame from the input manager since it might
// not always be active.
auto* inputManager = Environment::get().getInputManager();
if (inputManager)
inputManager->updateHead();
stagePose = poses.eye[(int)TrackedSpace::STAGE][(int)Side::LEFT_HAND];
viewPose = poses.eye[(int)TrackedSpace::VIEW][(int)Side::LEFT_HAND];
mView->setPredictedPose(viewPose);
side = (int)Side::LEFT_HAND;
}
else
{
stagePose = poses.eye[(int)TrackedSpace::STAGE][(int)Side::RIGHT_HAND];
viewPose = poses.eye[(int)TrackedSpace::VIEW][(int)Side::RIGHT_HAND];
mView->setPredictedPose(viewPose);
side = (int)Side::RIGHT_HAND;
}
if (!mXR->sessionRunning())
return;
auto& poses = mSession->predictedPoses(OpenXRSession::PredictionSlice::Predraw);
Pose viewPose{};
Pose stagePose{};
stagePose = poses.eye[(int)TrackedSpace::STAGE][side];
viewPose = poses.eye[(int)TrackedSpace::VIEW][side];
mView->setPredictedPose(viewPose);
auto viewMatrix = view.getCamera()->getViewMatrix();
auto modifiedViewMatrix = viewMatrix * mView->viewMatrix();

@ -1,17 +1,41 @@
#include "realisticcombat.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwmechanics/weapontype.hpp"
#include <iomanip>
namespace MWVR { namespace RealisticCombat {
static const char* stateToString(SwingState florida)
{
switch (florida)
{
case SwingState_Idle:
return "Idle";
case SwingState_Impact:
return "Impact";
case SwingState_Ready:
return "Ready";
case SwingState_Swinging:
return "Swinging";
}
}
StateMachine::StateMachine(MWWorld::Ptr ptr) : ptr(ptr) {}
bool StateMachine::canSwing()
{
if (velocity >= minVelocity)
if(swingType != ESM::Weapon::AT_Thrust || thrustVelocity >= 0.f)
return true;
return false;
}
// Actions common to all transitions
void StateMachine::transition(
SwingState newState)
{
if (newState == state)
throw std::logic_error("Cannot transition to current state");
Log(Debug::Verbose) << "Transition: " << stateToString(state) << " -> " << stateToString(newState);
maxSwingVelocity = 0.f;
timeSinceEnteredState = 0.f;
@ -28,11 +52,28 @@ void StateMachine::reset()
state = SwingState_Ready;
}
static bool isMeleeWeapon(int type)
{
if (MWMechanics::getWeaponType(type)->mWeaponClass != ESM::WeaponType::Melee)
return false;
if (type == ESM::Weapon::HandToHand)
return true;
if (type >= 0)
return true;
return false;
}
void StateMachine::update(float dt, bool enabled)
{
auto* session = Environment::get().getSession();
auto& handPose = session->predictedPoses().hands[(int)MWVR::TrackedSpace::STAGE][(int)MWVR::Side::RIGHT_HAND];
auto& headPose = session->predictedPoses().head[(int)MWVR::TrackedSpace::STAGE];
auto* world = MWBase::Environment::get().getWorld();
auto& predictedPoses = session->predictedPoses(OpenXRSession::PredictionSlice::Predraw);
auto& handPose = predictedPoses.hands[(int)MWVR::TrackedSpace::STAGE][(int)MWVR::Side::RIGHT_HAND];
auto& headPose = predictedPoses.head[(int)MWVR::TrackedSpace::STAGE];
auto weaponType = world->getActiveWeaponType();
enabled = enabled && isMeleeWeapon(weaponType);
if (mEnabled != enabled)
{
@ -47,17 +88,22 @@ void StateMachine::update(float dt, bool enabled)
// First determine direction of different swing types
// Thrust is radially out from the head. Which is the same as the position of the hand relative to the head, ignoring height component
osg::Vec3 thrustDirection = handPose.position - headPose.position;
thrustDirection.z() = 0;
thrustDirection.normalize();
// Discover orientation of weapon
osg::Quat weaponDir = handPose.orientation;
// Morrowind models do not hold weapons at a natural angle, so i rotate the hand forward
// to get a more natural angle on the weapon to allow more comfortable combat.
if (weaponType != ESM::Weapon::HandToHand)
weaponDir = osg::Quat(osg::PI_4, osg::Vec3{ 1,0,0 }) * weaponDir;
// Chop is straight down
osg::Vec3 chopDirection = osg::Vec3(0.f, 0.f, -1.f);
// Thrust means stabbing in the direction of the weapon
osg::Vec3 thrustDirection = weaponDir * osg::Vec3{ 0,1,0 };
// Swing is normal to the plane created by Chop x Thrust
osg::Vec3 slashDirection = chopDirection ^ thrustDirection;
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
@ -72,37 +118,36 @@ void StateMachine::update(float dt, bool enabled)
osg::Vec3 movement = handPose.position - previousPosition;
movementSinceEnteredState += movement.length();
previousPosition = handPose.position;
osg::Vec3 swingDirection = movement / dt;
osg::Vec3 swingVector = movement / dt;
// Compute swing velocity
// Unidirectional
thrustVelocity = swingDirection * thrustDirection;
thrustVelocity = swingVector * thrustDirection;
// Bidirectional
slashVelocity = std::abs(swingDirection * slashDirection);
chopVelocity = std::abs(swingDirection * chopDirection);
slashVelocity = std::abs(swingVector * slashDirection);
chopVelocity = std::abs(swingVector * chopDirection);
velocity = swingVector.length();
// Pick swing type based on greatest current velocity
// Note i use abs() of thrust velocity to prevent accidentally triggering
// chop or slash when player is withdrawing his limb.
if (std::abs(thrustVelocity) > slashVelocity && std::abs(thrustVelocity) > chopVelocity)
{
velocity = thrustVelocity;
swingType = ESM::Weapon::AT_Thrust;
}
else if (slashVelocity > chopVelocity)
{
velocity = slashVelocity;
swingType = ESM::Weapon::AT_Slash;
}
else
{
velocity = chopVelocity;
swingType = ESM::Weapon::AT_Chop;
}
switch (state)
{
case SwingState_Idle:
return update_idleState();
case SwingState_Ready:
return update_readyState();
case SwingState_Swinging:
@ -112,11 +157,21 @@ void StateMachine::update(float dt, bool enabled)
}
}
void StateMachine::update_idleState()
{
if (timeSinceEnteredState >= minimumPeriod)
transition_idleToReady();
}
void StateMachine::transition_idleToReady()
{
transition(SwingState_Ready);
}
void StateMachine::update_readyState()
{
if (velocity >= minVelocity)
if(timeSinceEnteredState >= minimumPeriod)
return transition_readyToSwinging();
if (canSwing())
return transition_readyToSwinging();
}
void StateMachine::transition_readyToSwinging()
@ -124,8 +179,9 @@ void StateMachine::transition_readyToSwinging()
shouldSwish = true;
transition(SwingState_Swinging);
// As an exception, update the new state immediately to allow
// same-frame impacts.
// As a special case, update the new state immediately to allow
// same-frame impacts. This is important if the player is moving
// at a velocity close to the minimum velocity.
update_swingingState();
}
@ -151,47 +207,47 @@ void StateMachine::update_swingingState()
maxSwingVelocity = std::max(velocity, maxSwingVelocity);
strength = std::min(1.f, (maxSwingVelocity - minVelocity) / maxVelocity);
if (velocity < minVelocity)
return transition_swingingToReady();
// Require a minimum period of swinging before a hit can be made
// This is to prevent annoying little microswings
// Require a minimum movement of the hand before a hit can be made
// This is to prevent microswings
if (movementSinceEnteredState > minimumPeriod)
{
playSwish();
// Note: calling hit with simulated=true to avoid side effects
if (ptr.getClass().hit(ptr, strength, swingType, true))
if (// When velocity falls below minimum, register the miss
!canSwing()
// Call hit with simulated=true to check for hit
|| ptr.getClass().hit(ptr, strength, swingType, true)
)
return transition_swingingToImpact();
}
// If velocity drops below minimum before minimum movement was achieved,
// drop back to ready
if (!canSwing())
return transition_swingingToReady();
}
void StateMachine::transition_swingingToReady()
{
if (movementSinceEnteredState > minimumPeriod)
{
playSwish();
ptr.getClass().hit(ptr, strength, swingType, false);
}
transition(SwingState_Ready);
}
void StateMachine::transition_swingingToImpact()
{
playSwish();
ptr.getClass().hit(ptr, strength, swingType, false);
transition(SwingState_Impact);
std::cout << "Transition: Swing -> Impact" << std::endl;
}
void StateMachine::update_impactState()
{
if (velocity < minVelocity)
return transition_impactToReady();
return transition_impactToIdle();
}
void StateMachine::transition_impactToReady()
void StateMachine::transition_impactToIdle()
{
transition(SwingState_Ready);
transition(SwingState_Idle);
std::cout << "Transition: Impact -> Ready" << std::endl;
}
}}

@ -13,9 +13,10 @@
namespace MWVR { namespace RealisticCombat {
enum SwingState
{
SwingState_Ready = 0,
SwingState_Swinging = 1,
SwingState_Impact = 2
SwingState_Idle = 0,
SwingState_Ready = 1,
SwingState_Swinging = 2,
SwingState_Impact = 3
};
struct StateMachine
@ -27,7 +28,7 @@ struct StateMachine
float velocity = 0.f;
float maxSwingVelocity = 0.f;
SwingState state = SwingState_Ready;
SwingState state = SwingState_Idle;
MWWorld::Ptr ptr = MWWorld::Ptr();
int swingType = -1;
float strength = 0.f;
@ -36,7 +37,7 @@ struct StateMachine
float slashVelocity{ 0.f };
float chopVelocity{ 0.f };
float minimumPeriod{ .5f };
float minimumPeriod{ .25f };
float timeSinceEnteredState = { 0.f };
float movementSinceEnteredState = { 0.f };
@ -47,6 +48,8 @@ struct StateMachine
StateMachine(MWWorld::Ptr ptr);
bool canSwing();
void playSwish();
void reset();
@ -54,6 +57,9 @@ struct StateMachine
void update(float dt, bool enabled);
void update_idleState();
void transition_idleToReady();
void update_readyState();
void transition_readyToSwinging();
@ -62,7 +68,7 @@ struct StateMachine
void transition_swingingToImpact();
void update_impactState();
void transition_impactToReady();
void transition_impactToIdle();
};
}}

@ -66,36 +66,33 @@
namespace MWVR
{
// This will work for a prototype. But finger/arm control might be better implemented using the
// existing animation system, implementing this as an animation source.
// But I'm not sure it would be since these are not classical animations.
// It would make it easier to control priority, and later allow for users to add their own stuff to animations based on VR/touch input.
// But openmw doesn't really have any concepts for user animation overrides as far as i can tell.
// Some weapon types, such as spellcast, are classified as melee even though they are not.
// All the fake melee types have negative type enum, but also so does hand to hand.
// I think this covers all the cases
static bool isMeleeWeapon(int type)
{
if (MWMechanics::getWeaponType(type)->mWeaponClass != ESM::WeaponType::Melee)
return false;
if (type == ESM::Weapon::HandToHand)
return true;
if (type >= 0)
return true;
return false;
}
/// Implements dummy control of the forearm, to control mesh/bone deformation of the hand.
/// Implements VR control of the forearm, to control mesh/bone deformation of the hand.
class ForearmController : public osg::NodeCallback
{
public:
ForearmController(osg::Node* relativeTo, SceneUtil::PositionAttitudeTransform* tracker);
ForearmController() = default;
void setEnabled(bool enabled) { mEnabled = enabled; };
void operator()(osg::Node* node, osg::NodeVisitor* nv);
private:
bool mEnabled = true;
osg::Quat mRotate{};
osg::Node* mRelativeTo;
osg::Matrix mOffset{ osg::Matrix::identity() };
bool mOffsetInitialized = false;
SceneUtil::PositionAttitudeTransform* mTracker;
bool mEnabled{ true };
};
ForearmController::ForearmController(osg::Node* relativeTo, SceneUtil::PositionAttitudeTransform* tracker)
: mRelativeTo(relativeTo)
, mTracker(tracker)
{
}
void ForearmController::operator()(osg::Node* node, osg::NodeVisitor* nv)
{
if (!mEnabled)
@ -105,44 +102,102 @@ void ForearmController::operator()(osg::Node* node, osg::NodeVisitor* nv)
}
osg::MatrixTransform* transform = static_cast<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)
{
side = (int)Side::LEFT_HAND;
// We base ourselves on the world position of the camera
// Ensure it is updated before placing the hands
// I'm sure this can be achieved properly by managing the scene graph better.
MWBase::Environment::get().getWorld()->getRenderingManager().getCamera()->updateCamera();
}
MWVR::Pose handStage = handPosesStage[side];
MWVR::Pose headStage = poses.head[(int)TrackedSpace::STAGE];
xr->playerScale(handStage);
xr->playerScale(headStage);
auto orientation = handStage.orientation;
auto position = handStage.position - headStage.position;
position = position * Environment::get().unitsPerMeter();
auto camera = xrViewer->mViewer->getCamera();
auto viewMatrix = camera->getViewMatrix();
// Align orientation with the game world
auto* inputManager = Environment::get().getInputManager();
if (inputManager)
{
// This is a bit of a hack.
// Trackers track hands, not forearms.
// But i have to transform the forearms to account for deformations,
// so i subtract the hand transform from the final transform to center the hands.
std::string handName = node->getName() == "Bip01 L Forearm" ? "Bip01 L Hand" : "Bip01 R Hand";
SceneUtil::FindByNameVisitor findHandVisitor(handName);
node->accept(findHandVisitor);
mOffset = findHandVisitor.mFoundNode->asTransform()->asMatrixTransform()->getInverseMatrix();
mOffsetInitialized = true;
auto playerYaw = osg::Quat(-inputManager->mYaw, osg::Vec3d(0, 0, 1));
position = playerYaw * position;
orientation = orientation * playerYaw;
}
// Add camera offset
osg::Vec3 viewPosition;
osg::Vec3 center;
osg::Vec3 up;
viewMatrix.getLookAt(viewPosition, center, up, 1.0);
position += viewPosition;
//// Morrowind's meshes do not point forward by default.
//// Declare the offsets static since they do not need to be recomputed.
static float VRbias = osg::DegreesToRadians(-90.f);
static osg::Quat yaw(-VRbias, osg::Vec3f(0, 0, 1));
static osg::Quat pitch(2.f * VRbias, osg::Vec3f(0, 1, 0));
static osg::Quat roll(2 * VRbias, osg::Vec3f(1, 0, 0));
orientation = yaw * orientation;
if (side == (int)Side::LEFT_HAND)
orientation = roll * orientation;
// Undo the wrist translate
auto* hand = transform->getChild(0);
auto handMatrix = hand->asTransform()->asMatrixTransform()->getMatrix();
position -= orientation * handMatrix.getTrans();
// Center hand mesh on tracking
// This is just an estimate from trial and error, any suggestion for improving this is welcome
position -= orientation * osg::Vec3{ 15,0,0 };
// Get current world transform of limb
osg::Matrix worldToLimb = osg::computeLocalToWorld(node->getParentalNodePaths()[0]);
// Get current world of the reference node
osg::Matrix worldReference = osg::Matrix::identity();
// New transform is reference node + tracker.
mTracker->computeLocalToWorldMatrix(worldReference, nullptr);
// Get hand
transform->setMatrix(mOffset * worldReference * osg::Matrix::inverse(worldToLimb) * transform->getMatrix());
// New transform based on tracking.
worldReference.preMultTranslate(position);
worldReference.preMultRotate(orientation);
// Finally, set transform
transform->setMatrix(worldReference * osg::Matrix::inverse(worldToLimb) * transform->getMatrix());
// TODO: Continued traversal is necessary to allow update of new hand poses such as gripping a weapon.
// But I want to disable idle animations.
Log(Debug::Verbose) << "Updating hand: " << node->getName();
// Omit nested callbacks to override animations of this node
osg::ref_ptr<osg::Callback> ncb = getNestedCallback();
setNestedCallback(nullptr);
traverse(node, nv);
setNestedCallback(ncb);
}
/// Implements control of a finger by overriding rotation
class FingerController : public osg::NodeCallback
{
public:
FingerController(osg::Quat rotate) : mRotate(rotate) {};
FingerController() {};
void setEnabled(bool enabled) { mEnabled = enabled; };
void operator()(osg::Node* node, osg::NodeVisitor* nv);
private:
bool mEnabled = true;
osg::Quat mRotate{};
};
void FingerController::operator()(osg::Node* node, osg::NodeVisitor* nv)
@ -153,36 +208,224 @@ void FingerController::operator()(osg::Node* node, osg::NodeVisitor* nv)
return;
}
// This update needs to hard override all other animation updates.
// To do this i need to make sure no further update calls are made.
// Therefore i do not traverse normally but instead explicitly fetch
// the children i want to update and update them here.
// I'm sure this could be done in a cleaner way
osg::Quat rotate{ 0,0,0,1 };
// TODO:
// To make finger pointing more natural each joint should angle down roughly 5-6 degrees per joint.
// But this creates obvious visual conflicts with the hand and the rest of the fingers which are not posed naturally,
// and looks particularly riddiculous when a weapon is drawn.
// Therefore, for the time being i will simply have them point straight forward relative to the current hand rotation.
// This leads to particularly awkward finger pointing while a weapon is drawn and should be replaced by a
// complete override of all hand animations by the end of 2090.
//////// Add a slight rotation down of the fingers to naturalize the pointing.
//////rotate = osg::Quat(osg::PI_4 / 8, osg::Vec3{ 0,1,0 }) * rotate;
//////if (node->getName() == "Bip01 R Finger1")
//////{
////// auto* world = MWBase::Environment::get().getWorld();
////// // Morrowind models do not hold weapons at a natural angle, so i rotate the hand forward
////// // to get a more natural angle on the weapon to allow more comfortable combat.
////// // Fingers need to angle back up to keep pointing natural.
////// if (world->getActiveWeaponType() >= 0)
////// {
////// rotate = osg::Quat(-osg::PI_4, osg::Vec3{ 0,1,0 }) * rotate;
////// }
//////}
// First, update the base of the finger to the overriding orientation
auto matrixTransform = node->asTransform()->asMatrixTransform();
auto matrix = matrixTransform->getMatrix();
matrix.setRotate(mRotate);
matrix.setRotate(rotate);
matrixTransform->setMatrix(matrix);
// Next update the tip.
// Note that for now both tips are just given osg::Quat(0,0,0,1) as that amounts to pointing forward.
auto tip = matrixTransform->getChild(0)->asTransform()->asMatrixTransform();
matrix = tip->getMatrix();
matrix.setRotate(mRotate);
tip->setMatrix(matrix);
// Finally, if pointing forward is enabled we need to intersect the scene to find where the player is pointing
// So that we can display a beam to visualize where the player is pointing.
// Omit nested callbacks to override animations of this node
osg::ref_ptr<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();
if (anim)
if (anim && node->getName() == "Bip01 R Finger1")
{
anim->updatePointerTarget();
}
}
/// Implements control of a finger by overriding rotation
class HandController : public osg::NodeCallback
{
public:
HandController() = default;
void setEnabled(bool enabled) { mEnabled = enabled; };
void operator()(osg::Node* node, osg::NodeVisitor* nv);
private:
bool mEnabled = true;
};
void HandController::operator()(osg::Node* node, osg::NodeVisitor* nv)
{
if (!mEnabled)
{
traverse(node, nv);
return;
}
float PI_2 = osg::PI_2;
if (node->getName() == "Bip01 L Hand")
PI_2 = -PI_2;
float PI_4 = PI_2 / 2.f;
osg::Quat rotate{ 0,0,0,1 };
auto* world = MWBase::Environment::get().getWorld();
auto windowManager = MWBase::Environment::get().getWindowManager();
auto weaponType = world->getActiveWeaponType();
// Morrowind models do not hold most weapons at a natural angle, so i rotate the hand
// to more natural angles on weapons to allow more comfortable combat.
if (!windowManager->isGuiMode())
{
switch (weaponType)
{
case ESM::Weapon::None:
case ESM::Weapon::HandToHand:
case ESM::Weapon::MarksmanThrown:
case ESM::Weapon::Spell:
case ESM::Weapon::Arrow:
case ESM::Weapon::Bolt:
// No adjustment
break;
case ESM::Weapon::MarksmanCrossbow:
// Crossbow points upwards. Assumedly because i am overriding hand animations.
rotate = osg::Quat(PI_4 / 1.05, osg::Vec3{ 0,1,0 }) * osg::Quat(0.06, osg::Vec3{ 0,0,1 });
break;
case ESM::Weapon::MarksmanBow:
// Bow points down by default, rotate it back up a little
rotate = osg::Quat(-PI_2 * .10f, osg::Vec3{ 0,1,0 });
break;
default:
// Melee weapons Need adjustment
rotate = osg::Quat(PI_4, osg::Vec3{ 0,1,0 });
break;
}
}
auto matrixTransform = node->asTransform()->asMatrixTransform();
auto matrix = matrixTransform->getMatrix();
matrix.setRotate(rotate);
matrixTransform->setMatrix(matrix);
// Omit nested callbacks to override animations of this node
osg::ref_ptr<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(
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.
, 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)));
mIndexFingerControllers[1] = osg::ref_ptr<FingerController> (new FingerController(osg::Quat(0, 0, 0, 1)));
for (int i = 0; i < 2; i++)
{
mIndexFingerControllers[i] = new FingerController;
mForearmControllers[i] = new ForearmController;
mHandControllers[i] = new HandController;
}
mWeaponDirectionTransform = new osg::MatrixTransform();
mWeaponDirectionTransform->setName("Weapon Direction");
mWeaponDirectionTransform->setUpdateCallback(new WeaponDirectionController);
mModelOffset->setName("ModelOffset");
createPointer();
mPointerGeometry = createPointerGeometry();
mPointerRescale = new osg::MatrixTransform();
mPointerRescale->addChild(mPointerGeometry);
mPointerTransform = new osg::MatrixTransform();
mPointerTransform->addChild(mPointerRescale);
mPointerTransform->setName("Pointer Transform");
// Morrowind's hands don't actually point forward, so we have to reorient the pointer.
mPointerTransform->setMatrix(osg::Matrix::rotate(osg::Quat(-osg::PI_2, osg::Vec3f(0, 0, 1))));
mWeaponPointerTransform = new osg::MatrixTransform();
mWeaponPointerTransform->addChild(mPointerGeometry);
mWeaponPointerTransform->setMatrix(
osg::Matrix::scale(0.f, 0.f, 0.f)
);
mWeaponPointerTransform->setName("Weapon Pointer");
mWeaponPointerTransform->setUpdateCallback(new WeaponPointerController);
mWeaponDirectionTransform->addChild(mWeaponPointerTransform);
}
VRAnimation::~VRAnimation() {};
@ -236,58 +504,29 @@ void VRAnimation::updateParts()
removeIndividualPart(ESM::PartReferenceType::PRT_LAnkle);
removeIndividualPart(ESM::PartReferenceType::PRT_RAnkle);
}
void VRAnimation::setPointForward(bool enabled)
{
auto found00 = mNodeMap.find("bip01 r finger1");
//auto weapon = mNodeMap.find("weapon");
if (found00 != mNodeMap.end())
auto finger = mNodeMap.find("bip01 r finger1");
if (finger != mNodeMap.end())
{
auto base_joint = found00->second;
auto base_joint = finger->second;
auto second_joint = base_joint->getChild(0)->asTransform()->asMatrixTransform();
assert(second_joint);
second_joint->removeChild(mPointerTransform);
//weapon->second->removeChild(mWeaponDirectionTransform);
mWeaponDirectionTransform->removeChild(mPointerGeometry);
base_joint->removeUpdateCallback(mIndexFingerControllers[0]);
second_joint->removeUpdateCallback(mIndexFingerControllers[1]);
if (enabled)
{
second_joint->addChild(mPointerTransform);
//weapon->second->addChild(mWeaponDirectionTransform);
mWeaponDirectionTransform->addChild(mPointerGeometry);
base_joint->addUpdateCallback(mIndexFingerControllers[0]);
second_joint->addUpdateCallback(mIndexFingerControllers[1]);
}
}
}
void VRAnimation::createPointer(void)
{
mPointerGeometry = createPointerGeometry();
mPointerTransform = new osg::MatrixTransform();
mPointerTransform->addChild(mPointerGeometry);
mPointerTransform->setName("Pointer Transform");
mWeaponPointerTransform = new osg::MatrixTransform();
mWeaponPointerTransform->addChild(mPointerGeometry);
mWeaponPointerTransform->setMatrix(
osg::Matrix::scale(64.f, 1.f, 1.f)
);
mWeaponPointerTransform->setName("Weapon Pointer");
mWeaponDirectionTransform = new osg::MatrixTransform();
mWeaponDirectionTransform->addChild(mWeaponPointerTransform);
mWeaponDirectionTransform->setMatrix(
osg::Matrix::rotate(osg::DegreesToRadians(-90.f), osg::Y_AXIS)
);
mWeaponDirectionTransform->setName("Weapon Direction");
mWeaponAdjustment = new osg::MatrixTransform();
//mWeaponAdjustment->addChild(mWeaponPointerTransform);
mWeaponAdjustment->setMatrix(
osg::Matrix::rotate(osg::DegreesToRadians(90.f), osg::Y_AXIS)
);
mWeaponAdjustment->setName("Weapon Adjustment");
mPointerTransform->removeChild(mPointerRescale);
if (enabled)
mPointerTransform->addChild(mPointerRescale);
}
osg::ref_ptr<osg::Geometry> VRAnimation::createPointerGeometry(void)
@ -300,8 +539,8 @@ osg::ref_ptr<osg::Geometry> VRAnimation::createPointerGeometry(void)
osg::Vec3 vertices[]{
{0, 0, 0}, // origin
{1, 1, -1}, // top_left
{1, -1, -1}, // bottom_left
{1, -1, 1}, // bottom_right
{-1, 1, -1}, // bottom_left
{-1, 1, 1}, // bottom_right
{1, 1, 1}, // top_right
};
@ -340,8 +579,6 @@ osg::ref_ptr<osg::Geometry> VRAnimation::createPointerGeometry(void)
osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));
geometry->setVertexArray(vertexArray);
geometry->setColorArray(colorArray, osg::Array::BIND_PER_VERTEX);
geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, numVertices));
@ -367,58 +604,46 @@ void VRAnimation::addControllers()
{
NpcAnimation::addControllers();
// TODO:
// Those controllers should be made using the openxr session *here* rather than magicking up
// a couple nodes by searching the scene graph.
//mNodeMap, mActiveControllers, mObjectRoot.get()
SceneUtil::FindByNameVisitor findXRVisitor("OpenXRRoot", osg::NodeVisitor::TRAVERSE_PARENTS);
getObjectRoot()->accept(findXRVisitor);
auto* xrRoot = findXRVisitor.mFoundNode;
if (!xrRoot)
{
throw std::logic_error("Viewmode is VM_VRHeadless but OpenXRRoot does not exist");
}
for (int i = 0; i < 2; ++i)
{
mHandControllers[i] = nullptr;
SceneUtil::FindByNameVisitor findTrackerVisitor(i == 0 ? "tracker l hand" : "tracker r hand");
xrRoot->accept(findTrackerVisitor);
if (!findTrackerVisitor.mFoundNode)
continue;
SceneUtil::PositionAttitudeTransform* tracker = dynamic_cast<SceneUtil::PositionAttitudeTransform*>(findTrackerVisitor.mFoundNode);
auto found = mNodeMap.find(i == 0 ? "bip01 l forearm" : "bip01 r forearm");
if (found != mNodeMap.end())
auto forearm = mNodeMap.find(i == 0 ? "bip01 l forearm" : "bip01 r forearm");
if (forearm != mNodeMap.end())
{
osg::Node* node = found->second;
mForearmControllers[i] = new ForearmController(mObjectRoot, tracker);
auto node = forearm->second;
node->removeUpdateCallback(mForearmControllers[i]);
node->addUpdateCallback(mForearmControllers[i]);
mActiveControllers.insert(std::make_pair(node, mForearmControllers[i]));
}
auto hand = mNodeMap.find(i == 0 ? "bip01 l hand" : "bip01 r hand");
if (hand != mNodeMap.end())
{
auto node = hand->second;
node->removeUpdateCallback(mHandControllers[i]);
node->addUpdateCallback(mHandControllers[i]);
}
}
auto hand = mNodeMap.find("bip01 r hand");
if (hand != mNodeMap.end())
{
hand->second->removeChild(mWeaponDirectionTransform);
hand->second->addChild(mWeaponDirectionTransform);
}
auto finger = mNodeMap.find("bip01 r finger11");
if (finger != mNodeMap.end())
{
finger->second->removeChild(mPointerTransform);
finger->second->addChild(mPointerTransform);
}
auto parent = mObjectRoot->getParent(0);
if (parent->getName() == "Player Root")
{
auto group = parent->asGroup();
group->removeChildren(0, parent->getNumChildren());
group->addChild(mModelOffset);
mModelOffset->addChild(mObjectRoot);
auto weapon = mNodeMap.find("weapon");
weapon->second->addChild(mWeaponDirectionTransform);
}
}
void VRAnimation::enableHeadAnimation(bool)
{
@ -454,13 +679,13 @@ void VRAnimation::updatePointerTarget()
auto* world = MWBase::Environment::get().getWorld();
if (world)
{
mPointerTransform->setMatrix(osg::Matrix::scale(1, 1, 1));
mPointerRescale->setMatrix(osg::Matrix::scale(1, 1, 1));
mDistanceToPointerTarget = world->getTargetObject(mPointerTarget, mPointerTransform);
if(mDistanceToPointerTarget >= 0)
mPointerTransform->setMatrix(osg::Matrix::scale(mDistanceToPointerTarget, 0.25f, 0.25f));
mPointerRescale->setMatrix(osg::Matrix::scale(0.25f, mDistanceToPointerTarget, 0.25f));
else
mPointerTransform->setMatrix(osg::Matrix::scale(10000.f, 0.25f, 0.25f));
mPointerRescale->setMatrix(osg::Matrix::scale(0.25f, 10000.f, 0.25f));
}
}

@ -65,18 +65,17 @@ public:
void updatePointerTarget();
public:
void createPointer(void);
static osg::ref_ptr<osg::Geometry> createPointerGeometry(void);
public:
std::shared_ptr<OpenXRSession> mSession;
ForearmController* mForearmControllers[2]{};
HandController* mHandControllers[2]{};
osg::ref_ptr<ForearmController> mForearmControllers[2];
osg::ref_ptr<HandController> mHandControllers[2];
osg::ref_ptr<FingerController> mIndexFingerControllers[2];
osg::ref_ptr<osg::MatrixTransform> mModelOffset;
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> mWeaponAdjustment{ nullptr };
osg::ref_ptr<osg::MatrixTransform> mWeaponDirectionTransform{ nullptr };
osg::ref_ptr<osg::MatrixTransform> mWeaponPointerTransform{ nullptr };
MWRender::RayResult mPointerTarget{};

@ -32,9 +32,6 @@ void MWVR::Environment::cleanup()
if (mMenuManager)
delete mMenuManager;
mMenuManager = nullptr;
if (mPlayerAnimation)
delete mPlayerAnimation;
mPlayerAnimation = nullptr;
if (mViewer)
delete mViewer;
mViewer = nullptr;

@ -43,6 +43,11 @@
#include "../mwphysics/physicssystem.hpp"
#ifdef USE_OPENXR
#include "../mwvr/vrenvironment.hpp"
#include "../mwvr/vranimation.hpp"
#endif
namespace
{
ESM::EffectList getMagicBoltData(std::vector<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;
osg::Quat orient;
#ifdef USE_OPENXR
if (caster == MWBase::Environment::get().getWorld()->getPlayerPtr())
{
auto* anim = MWVR::Environment::get().getPlayerAnimation();
osg::Matrix worldMatrix = osg::computeLocalToWorld(anim->mWeaponDirectionTransform->getParentalNodePaths()[0]);
orient = worldMatrix.getRotate();
pos = worldMatrix.getTrans();
}
else
#endif
if (caster.getClass().isActor())
orient = osg::Quat(caster.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
* osg::Quat(caster.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));

@ -1154,7 +1154,7 @@ namespace MWWorld
if (ptr == getPlayerPtr())
{
#ifdef USE_OPENXR
// TODO: Configurable realistic fighting.
// TODO: Configurable realistic fighting ?
// Use current aim of weapon to impact
// TODO: Use bounding box of weapon instead ?
@ -3063,8 +3063,11 @@ namespace MWWorld
// for player we can take faced object first
MWWorld::Ptr target;
#ifndef USE_OPENXR
// Does not apply to VR
if (actor == MWMechanics::getPlayer())
target = getFacedObject();
#endif
// if the faced object can not be activated, do not use it
if (!target.isEmpty() && !target.getClass().hasToolTip(target))
@ -3101,10 +3104,21 @@ namespace MWWorld
osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
* osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
#ifdef USE_OPENXR
if (actor == MWMechanics::getPlayer())
{
auto* anim = MWVR::Environment::get().getPlayerAnimation();
osg::Matrix worldMatrix = osg::computeLocalToWorld(anim->mWeaponDirectionTransform->getParentalNodePaths()[0]);
origin = worldMatrix.getTrans();
orient = worldMatrix.getRotate();
}
#endif
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
float distance = getMaxActivationDistance();
osg::Vec3f dest = origin + direction * distance;
MWRender::RayResult result2 = mRendering->castRay(origin, dest, true, true);
float dist1 = std::numeric_limits<float>::max();
@ -4000,4 +4014,35 @@ namespace MWWorld
{
return mPhysics.get();
}
int World::getActiveWeaponType(void)
{
if (mPlayer)
{
if (mPlayer->getDrawState() == MWMechanics::DrawState_Nothing)
return ESM::Weapon::Type::None;
if (mPlayer->getDrawState() == MWMechanics::DrawState_Spell)
return ESM::Weapon::Type::Spell;
MWWorld::Ptr ptr = mPlayer->getPlayer();
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
MWWorld::ConstContainerStoreIterator it = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (it != invStore.end())
{
if (it->getTypeName() == typeid(ESM::Weapon).name())
return ESM::Weapon::Type(it->get<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;
/// @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…
Cancel
Save