mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-20 06:53:52 +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
|
||||
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
|
||||
|
||||
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,12 +760,17 @@ void OpenXRInputManager::updateActivationIndication(void)
|
|||
bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode();
|
||||
bool show = guiMode | mActivationIndication;
|
||||
if (mPlayer)
|
||||
{
|
||||
if (show != mPlayer->getPointing())
|
||||
{
|
||||
mPlayer->setPointing(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++;
|
||||
}
|
||||
|
||||
const PoseSets& OpenXRSession::predictedPoses(PredictionSlice slice)
|
||||
{
|
||||
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();
|
||||
if (!xr->sessionRunning())
|
||||
mIsRunning = xr->sessionRunning();
|
||||
if (!mIsRunning)
|
||||
return;
|
||||
|
||||
Timer timer("OpenXRSession::waitFrame");
|
||||
xr->waitFrame();
|
||||
timer.checkpoint("waitFrame");
|
||||
predictNext(0);
|
||||
|
||||
mPredictionsReady = true;
|
||||
|
||||
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;
|
||||
|
||||
// Chop is straight down
|
||||
osg::Vec3 chopDirection = osg::Vec3(0.f, 0.f, -1.f);
|
||||
// Morrowind models do not hold weapons at a natural angle, so i rotate the hand forward
|
||||
// to get a more natural angle on the weapon to allow more comfortable combat.
|
||||
if (weaponType != ESM::Weapon::HandToHand)
|
||||
weaponDir = osg::Quat(osg::PI_4, osg::Vec3{ 1,0,0 }) * weaponDir;
|
||||
|
||||
// Swing is normal to the plane created by Chop x Thrust
|
||||
osg::Vec3 slashDirection = chopDirection ^ thrustDirection;
|
||||
slashDirection.normalize();
|
||||
// Thrust means stabbing in the direction of the weapon
|
||||
osg::Vec3 thrustDirection = weaponDir * osg::Vec3{ 0,1,0 };
|
||||
|
||||
// Chop is vertical, relative to the orientation of the weapon
|
||||
osg::Vec3 chopDirection = weaponDir * osg::Vec3{ 0,0,1 };
|
||||
|
||||
// Swing is horizontal, relative to the orientation of the weapon
|
||||
osg::Vec3 slashDirection = weaponDir * osg::Vec3{ 1,0,0 };
|
||||
|
||||
|
||||
// Next determine current hand movement
|
||||
|
@ -72,37 +118,36 @@ void StateMachine::update(float dt, bool enabled)
|
|||
osg::Vec3 movement = handPose.position - previousPosition;
|
||||
movementSinceEnteredState += movement.length();
|
||||
previousPosition = handPose.position;
|
||||
osg::Vec3 swingDirection = movement / dt;
|
||||
osg::Vec3 swingVector = movement / dt;
|
||||
|
||||
// Compute swing velocity
|
||||
// Unidirectional
|
||||
thrustVelocity = swingDirection * thrustDirection;
|
||||
thrustVelocity = swingVector * thrustDirection;
|
||||
// Bidirectional
|
||||
slashVelocity = std::abs(swingDirection * slashDirection);
|
||||
chopVelocity = std::abs(swingDirection * chopDirection);
|
||||
slashVelocity = std::abs(swingVector * slashDirection);
|
||||
chopVelocity = std::abs(swingVector * chopDirection);
|
||||
velocity = swingVector.length();
|
||||
|
||||
// Pick swing type based on greatest current velocity
|
||||
// Note i use abs() of thrust velocity to prevent accidentally triggering
|
||||
// chop or slash when player is withdrawing his limb.
|
||||
if (std::abs(thrustVelocity) > slashVelocity && std::abs(thrustVelocity) > chopVelocity)
|
||||
{
|
||||
velocity = thrustVelocity;
|
||||
swingType = ESM::Weapon::AT_Thrust;
|
||||
}
|
||||
else if (slashVelocity > chopVelocity)
|
||||
{
|
||||
velocity = slashVelocity;
|
||||
swingType = ESM::Weapon::AT_Slash;
|
||||
}
|
||||
else
|
||||
{
|
||||
velocity = chopVelocity;
|
||||
swingType = ESM::Weapon::AT_Chop;
|
||||
}
|
||||
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case SwingState_Idle:
|
||||
return update_idleState();
|
||||
case SwingState_Ready:
|
||||
return update_readyState();
|
||||
case SwingState_Swinging:
|
||||
|
@ -112,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()
|
||||
{
|
||||
if (velocity >= minVelocity)
|
||||
if(timeSinceEnteredState >= minimumPeriod)
|
||||
if (canSwing())
|
||||
return 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)
|
||||
{
|
||||
// This is a bit of a hack.
|
||||
// Trackers track hands, not forearms.
|
||||
// But i have to transform the forearms to account for deformations,
|
||||
// so i subtract the hand transform from the final transform to center the hands.
|
||||
std::string handName = node->getName() == "Bip01 L Forearm" ? "Bip01 L Hand" : "Bip01 R Hand";
|
||||
SceneUtil::FindByNameVisitor findHandVisitor(handName);
|
||||
node->accept(findHandVisitor);
|
||||
mOffset = findHandVisitor.mFoundNode->asTransform()->asMatrixTransform()->getInverseMatrix();
|
||||
mOffsetInitialized = true;
|
||||
side = (int)Side::LEFT_HAND;
|
||||
// We base ourselves on the world position of the camera
|
||||
// Ensure it is updated before placing the hands
|
||||
// I'm sure this can be achieved properly by managing the scene graph better.
|
||||
MWBase::Environment::get().getWorld()->getRenderingManager().getCamera()->updateCamera();
|
||||
}
|
||||
|
||||
MWVR::Pose handStage = handPosesStage[side];
|
||||
MWVR::Pose headStage = poses.head[(int)TrackedSpace::STAGE];
|
||||
xr->playerScale(handStage);
|
||||
xr->playerScale(headStage);
|
||||
|
||||
auto orientation = handStage.orientation;
|
||||
|
||||
auto position = handStage.position - headStage.position;
|
||||
position = position * Environment::get().unitsPerMeter();
|
||||
|
||||
auto camera = xrViewer->mViewer->getCamera();
|
||||
auto viewMatrix = camera->getViewMatrix();
|
||||
|
||||
|
||||
// Align orientation with the game world
|
||||
auto* inputManager = Environment::get().getInputManager();
|
||||
if (inputManager)
|
||||
{
|
||||
auto playerYaw = osg::Quat(-inputManager->mYaw, osg::Vec3d(0, 0, 1));
|
||||
position = playerYaw * position;
|
||||
orientation = orientation * playerYaw;
|
||||
}
|
||||
|
||||
// Add camera offset
|
||||
osg::Vec3 viewPosition;
|
||||
osg::Vec3 center;
|
||||
osg::Vec3 up;
|
||||
|
||||
viewMatrix.getLookAt(viewPosition, center, up, 1.0);
|
||||
position += viewPosition;
|
||||
|
||||
//// Morrowind's meshes do not point forward by default.
|
||||
//// Declare the offsets static since they do not need to be recomputed.
|
||||
static float VRbias = osg::DegreesToRadians(-90.f);
|
||||
static osg::Quat yaw(-VRbias, osg::Vec3f(0, 0, 1));
|
||||
static osg::Quat pitch(2.f * VRbias, osg::Vec3f(0, 1, 0));
|
||||
static osg::Quat roll(2 * VRbias, osg::Vec3f(1, 0, 0));
|
||||
orientation = yaw * orientation;
|
||||
if (side == (int)Side::LEFT_HAND)
|
||||
orientation = roll * orientation;
|
||||
|
||||
// Undo the wrist translate
|
||||
auto* hand = transform->getChild(0);
|
||||
auto handMatrix = hand->asTransform()->asMatrixTransform()->getMatrix();
|
||||
position -= orientation * handMatrix.getTrans();
|
||||
|
||||
// Center hand mesh on tracking
|
||||
// This is just an estimate from trial and error, any suggestion for improving this is welcome
|
||||
position -= orientation * osg::Vec3{ 15,0,0 };
|
||||
|
||||
// Get current world transform of limb
|
||||
osg::Matrix worldToLimb = osg::computeLocalToWorld(node->getParentalNodePaths()[0]);
|
||||
// Get current world of the reference node
|
||||
osg::Matrix worldReference = osg::Matrix::identity();
|
||||
// New transform is reference node + tracker.
|
||||
mTracker->computeLocalToWorldMatrix(worldReference, nullptr);
|
||||
// Get hand
|
||||
transform->setMatrix(mOffset * worldReference * osg::Matrix::inverse(worldToLimb) * transform->getMatrix());
|
||||
// New transform based on tracking.
|
||||
worldReference.preMultTranslate(position);
|
||||
worldReference.preMultRotate(orientation);
|
||||
|
||||
// Finally, set transform
|
||||
transform->setMatrix(worldReference * osg::Matrix::inverse(worldToLimb) * transform->getMatrix());
|
||||
|
||||
// TODO: Continued traversal is necessary to allow update of new hand poses such as gripping a weapon.
|
||||
// But I want to disable idle animations.
|
||||
Log(Debug::Verbose) << "Updating hand: " << node->getName();
|
||||
|
||||
// Omit nested callbacks to override animations of this node
|
||||
osg::ref_ptr<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…
Reference in a new issue