1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-02-21 19:09:41 +00:00

Fixed recentering adjusting the player's orientation. Cell changes should now retain the proper orientation. Added support for seated play.

This commit is contained in:
madsbuvi 2021-02-28 16:01:03 +01:00
parent 7986def3db
commit 3347cdfe30
17 changed files with 186 additions and 98 deletions

View file

@ -158,10 +158,10 @@ namespace MWPhysics
// But 2. is not so obvious. I guess it's doable if i compute the direction between current position and the player's
// position in the VR stage, and just let it catch up at the character's own move speed, but it still needs to reach the position as exactly as possible.
if (isPlayer)
auto* session = MWVR::Environment::get().getSession();
if (session)
{
auto* session = MWVR::Environment::get().getSession();
if (session)
if (isPlayer)
{
float pitch = 0.f;
float yaw = 0.f;
@ -238,81 +238,90 @@ namespace MWPhysics
#ifdef USE_OPENXR
// Catch the player character up to the real world position of the player.
// But only if play is not seated.
// TODO: Hack.
if (isPlayer && !world->getPlayer().isDisabled())
if (isPlayer)
{
bool shouldMove = true;
if (session && session->seatedPlay())
shouldMove = false;
if (world->getPlayer().isDisabled())
shouldMove = false;
auto* inputManager = reinterpret_cast<MWVR::VRCamera*>(MWBase::Environment::get().getWorld()->getRenderingManager().getCamera());
osg::Vec3 headOffset = inputManager->headOffset();
osg::Vec3 trackingOffset = headOffset;
// Player's tracking height should not affect character position
trackingOffset.z() = 0;
float remainingTime = time;
bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying;
float remainder = 1.f;
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f && remainder > 0.01; ++iterations)
if (shouldMove)
{
osg::Vec3 toMove = trackingOffset * remainder;
osg::Vec3 nextpos = newPosition + toMove;
auto* inputManager = reinterpret_cast<MWVR::VRCamera*>(MWBase::Environment::get().getWorld()->getRenderingManager().getCamera());
if ((newPosition - nextpos).length2() > 0.0001)
osg::Vec3 headOffset = inputManager->headOffset();
osg::Vec3 trackingOffset = headOffset;
// Player's tracking height should not affect character position
trackingOffset.z() = 0;
float remainingTime = time;
bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying;
float remainder = 1.f;
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f && remainder > 0.01; ++iterations)
{
// trace to where character would go if there were no obstructions
tracer.doTrace(colobj, newPosition, nextpos, collisionWorld);
osg::Vec3 toMove = trackingOffset * remainder;
osg::Vec3 nextpos = newPosition + toMove;
// check for obstructions
if (tracer.mFraction >= 1.0f)
if ((newPosition - nextpos).length2() > 0.0001)
{
newPosition = tracer.mEndPos; // ok to move, so set newPosition
// trace to where character would go if there were no obstructions
tracer.doTrace(colobj, newPosition, nextpos, collisionWorld);
// check for obstructions
if (tracer.mFraction >= 1.0f)
{
newPosition = tracer.mEndPos; // ok to move, so set newPosition
remainder = 0.f;
break;
}
}
else
{
// The current position and next position are nearly the same, so just exit.
// Note: Bullet can trigger an assert in debug modes if the positions
// are the same, since that causes it to attempt to normalize a zero
// length vector (which can also happen with nearly identical vectors, since
// precision can be lost due to any math Bullet does internally). Since we
// aren't performing any collision detection, we want to reject the next
// position, so that we don't slowly move inside another object.
remainder = 0.f;
break;
}
}
else
{
// The current position and next position are nearly the same, so just exit.
// Note: Bullet can trigger an assert in debug modes if the positions
// are the same, since that causes it to attempt to normalize a zero
// length vector (which can also happen with nearly identical vectors, since
// precision can be lost due to any math Bullet does internally). Since we
// aren't performing any collision detection, we want to reject the next
// position, so that we don't slowly move inside another object.
remainder = 0.f;
break;
if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel)
seenGround = true;
// We are touching something.
if (tracer.mFraction < 1E-9f)
{
// Try to separate by backing off slighly to unstuck the solver
osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-2f;
newPosition += backOff;
}
// We hit something. Check if we can step up.
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z();
osg::Vec3f oldPosition = newPosition;
bool result = false;
if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject))
{
// Try to step up onto it.
// NOTE: stepMove does not allow stepping over, modifies newPosition if successful
result = stepper.step(newPosition, toMove, remainingTime, seenGround, iterations == 0);
remainder = remainingTime / time;
}
}
if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel)
seenGround = true;
// We are touching something.
if (tracer.mFraction < 1E-9f)
{
// Try to separate by backing off slighly to unstuck the solver
osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-2f;
newPosition += backOff;
}
// We hit something. Check if we can step up.
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z();
osg::Vec3f oldPosition = newPosition;
bool result = false;
if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject))
{
// Try to step up onto it.
// NOTE: stepMove does not allow stepping over, modifies newPosition if successful
result = stepper.step(newPosition, toMove, remainingTime, seenGround, iterations == 0);
remainder = remainingTime / time;
}
// Try not to lose any tracking
osg::Vec3 moved = newPosition - actor.mPosition;
headOffset.x() -= moved.x();
headOffset.y() -= moved.y();
inputManager->setHeadOffset(headOffset);
}
// Try not to lose any tracking
osg::Vec3 moved = newPosition - actor.mPosition;
headOffset.x() -= moved.x();
headOffset.y() -= moved.y();
inputManager->setHeadOffset(headOffset);
}
#endif

View file

@ -90,8 +90,13 @@ namespace MWVR
CHECK_XRCMD(xrCreateReferenceSpace(mSession, &createInfo, &mReferenceSpaceView));
createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
CHECK_XRCMD(xrCreateReferenceSpace(mSession, &createInfo, &mReferenceSpaceStage));
createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
CHECK_XRCMD(xrCreateReferenceSpace(mSession, &createInfo, &mReferenceSpaceLocal));
}
// Default to using the stage
mReferenceSpace = mReferenceSpaceStage;
{ // Read and log graphics properties for the swapchain
xrGetSystemProperties(mInstance, mSystemId, &mSystemProperties);
@ -302,7 +307,7 @@ namespace MWVR
compositionLayerProjectionViews[(int)Side::LEFT_SIDE] = toXR((*layerStack)[(int)Side::LEFT_SIDE]);
compositionLayerProjectionViews[(int)Side::RIGHT_SIDE] = toXR((*layerStack)[(int)Side::RIGHT_SIDE]);
layer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
layer.space = getReferenceSpace(ReferenceSpace::STAGE);
layer.space = getReferenceSpace();
layer.viewCount = 2;
layer.views = compositionLayerProjectionViews.data();
auto* xrLayerStack = reinterpret_cast<XrCompositionLayerBaseHeader*>(&layer);
@ -591,14 +596,9 @@ namespace MWVR
return XrFovf{ fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown };
}
XrSpace OpenXRManagerImpl::getReferenceSpace(ReferenceSpace space)
XrSpace OpenXRManagerImpl::getReferenceSpace()
{
XrSpace referenceSpace = XR_NULL_HANDLE;
if (space == ReferenceSpace::STAGE)
referenceSpace = mReferenceSpaceStage;
if (space == ReferenceSpace::VIEW)
referenceSpace = mReferenceSpaceView;
return referenceSpace;
return mReferenceSpace;
}
bool OpenXRManagerImpl::xrExtensionIsEnabled(const char* extensionName) const

View file

@ -56,7 +56,7 @@ namespace MWVR
long long getLastPredictedDisplayTime();
long long getLastPredictedDisplayPeriod();
std::array<SwapchainConfig, 2> getRecommendedSwapchainConfig() const;
XrSpace getReferenceSpace(ReferenceSpace space);
XrSpace getReferenceSpace();
XrSession xrSession() const { return mSession; };
XrInstance xrInstance() const { return mInstance; };
bool xrExtensionIsEnabled(const char* extensionName) const;
@ -69,6 +69,7 @@ namespace MWVR
void eraseFormat(int64_t format);
OpenXRPlatform& platform() { return mPlatform; };
protected:
void setupExtensionsAndLayers();
void setupDebugMessenger(void);
@ -97,6 +98,8 @@ namespace MWVR
std::array<XrViewConfigurationView, 2> mConfigViews{ { {XR_TYPE_VIEW_CONFIGURATION_VIEW}, {XR_TYPE_VIEW_CONFIGURATION_VIEW} } };
XrSpace mReferenceSpaceView = XR_NULL_HANDLE;
XrSpace mReferenceSpaceStage = XR_NULL_HANDLE;
XrSpace mReferenceSpaceLocal = XR_NULL_HANDLE;
XrSpace mReferenceSpace = XR_NULL_HANDLE;
XrFrameState mFrameState{};
XrSessionState mSessionState = XR_SESSION_STATE_UNKNOWN;
XrDebugUtilsMessengerEXT mDebugMessenger{ nullptr };

View file

@ -469,6 +469,7 @@ namespace MWVR
float realHeight = Settings::Manager::getFloat("real height", "VR");
float sizeFactor = charHeight / realHeight;
Environment::get().getSession()->setPlayerScale(sizeFactor);
Environment::get().getSession()->setEyeLevel(charHeightBase - 0.15f); // approximation
}
void VRAnimation::setFingerPointingMode(bool enabled)

View file

@ -12,6 +12,9 @@
#include "../mwbase/world.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
#include "../mwmechanics/movement.hpp"
#include <osg/Quat>
@ -45,16 +48,35 @@ namespace MWVR
// Move position of head to center of character
// Z should not be affected
mHeadOffset = osg::Vec3(0, 0, 0);
mHeadOffset.z() = mHeadPose.position.z();
mHeadOffset.x() = 0;
mHeadOffset.y() = 0;
// Adjust orientation to zero yaw
float yaw = 0.f;
float pitch = 0.f;
float roll = 0.f;
getEulerAngles(mHeadPose.orientation, yaw, pitch, roll);
mYawOffset = -yaw;
auto* session = Environment::get().getSession();
if (session->seatedPlay() && mShouldResetZ)
{
// Adjust offset to place the current pose roughly at eye level
mHeadOffset.z() = session->eyeLevel() * Constants::UnitsPerMeter;
}
else
{
mHeadOffset.z() = mHeadPose.position.z();
}
mShouldResetZ = false;
// When the cell changes, the game rotates the character appropriately.
// To respect this, reset yaw offset to make our yaw match the character.
MWBase::World* world = MWBase::Environment::get().getWorld();
if (world)
{
auto& player = world->getPlayer();
auto playerPtr = player.getPlayer();
const auto& data = playerPtr.getRefData();
float yaw = 0.f;
float pitch = 0.f;
float roll = 0.f;
getEulerAngles(mHeadPose.orientation, yaw, pitch, roll);
mYawOffset = data.getPosition().rot[2] - yaw;
}
mShouldRecenter = false;
Log(Debug::Verbose) << "Recentered";
}
@ -194,4 +216,12 @@ namespace MWVR
{
return osg::Quat(mYawOffset, osg::Vec3(0, 0, -1));
}
void VRCamera::requestRecenter(bool resetZ)
{
mShouldRecenter = true;
// Use OR so we don't a pending reset of Z.
mShouldResetZ |= resetZ;
}
}

View file

@ -57,7 +57,7 @@ namespace MWVR
void rotateStage(float yaw) { mYawOffset += yaw; }
void requestRecenter() { mShouldRecenter = true; }
void requestRecenter(bool resetZ);
const osg::Vec3& headOffset() const { return mHeadOffset; }
@ -75,6 +75,7 @@ namespace MWVR
Pose mHeadPose{};
osg::Vec3 mHeadOffset{ 0,0,0 };
bool mShouldRecenter{ true };
bool mShouldResetZ{ true };
bool mHasTrackingData{ false };
float mYawOffset{ 0.f };
bool mShouldTrackPlayerCharacter{ false };

View file

@ -42,7 +42,7 @@ namespace MWVR
mPrevious = mValue;
auto* xr = Environment::get().getManager();
XrSpace referenceSpace = xr->impl().getReferenceSpace(ReferenceSpace::STAGE);
XrSpace referenceSpace = xr->impl().getReferenceSpace();
XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION };
XrSpaceVelocity velocity{ XR_TYPE_SPACE_VELOCITY };

View file

@ -280,10 +280,10 @@ namespace MWVR
mAxisDeadzone->setDeadzoneRadius(deadzoneRadius);
}
void VRInputManager::requestRecenter()
void VRInputManager::requestRecenter(bool resetZ)
{
// TODO: Hack, should have a cleaner way of accessing this
reinterpret_cast<VRCamera*>(MWBase::Environment::get().getWorld()->getRenderingManager().getCamera())->requestRecenter();
reinterpret_cast<VRCamera*>(MWBase::Environment::get().getWorld()->getRenderingManager().getCamera())->requestRecenter(resetZ);
}
VRInputManager::VRInputManager(
@ -723,7 +723,7 @@ namespace MWVR
case A_Recenter:
vrGuiManager->updateTracking();
if (!MWBase::Environment::get().getWindowManager()->isGuiMode())
requestRecenter();
requestRecenter(true);
break;
case MWInput::A_Use:
if (mActivationIndication || MWBase::Environment::get().getWindowManager()->isGuiMode())

View file

@ -46,7 +46,7 @@ namespace MWVR
void update(float dt, bool disableControls = false, bool disableEvents = false) override;
/// Set current offset to 0 and re-align VR stage.
void requestRecenter();
void requestRecenter(bool resetZ);
/// Tracking pose of the given limb at the given predicted time
Pose getLimbPose(int64_t time, TrackedLimb limb);

View file

@ -6,7 +6,9 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/statemanager.hpp"
#include "vrmetamenu.hpp"
#include "vrenvironment.hpp"
#include "vrinputmanager.hpp"
namespace MWVR
{
@ -94,6 +96,11 @@ namespace MWVR
MWBase::Environment::get().getStateManager()->quickSave();
}
void VrMetaMenu::onRecenter()
{
Environment::get().getInputManager()->requestRecenter(true);
}
void VrMetaMenu::close()
{
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_VrMetaMenu);
@ -118,7 +125,9 @@ namespace MWVR
else if (name == "quickload")
onQuickLoad();
else if (name == "quicksave")
onQuickSave();
onQuickSave();
else if (name == "recenter")
onRecenter();
}
bool VrMetaMenu::exit()
@ -128,7 +137,7 @@ namespace MWVR
void VrMetaMenu::updateMenu()
{
static std::vector<std::string> buttons{ "return", "quicksave", "quickload", "console", "inventory", "journal", "rest", "quickmenu", "gamemenu" };
static std::vector<std::string> buttons{ "return", "recenter", "quicksave", "quickload", "console", "inventory", "journal", "rest", "quickmenu", "gamemenu" };
if(mButtons.empty())
for (std::string& buttonId : buttons)

View file

@ -50,6 +50,7 @@ namespace MWVR
void onQuickMenu();
void onQuickLoad();
void onQuickSave();
void onRecenter();
void close();
void updateMenu();

View file

@ -52,6 +52,7 @@ namespace MWVR
VRSession::VRSession()
{
mHandDirectedMovement = Settings::Manager::getBool("hand directed movement", "VR");
mSeatedPlay = Settings::Manager::getBool("seated play", "VR");
}
VRSession::~VRSession()
@ -71,10 +72,8 @@ namespace MWVR
{
for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it)
{
if (it->first == "VR" && it->second == "hand directed movement")
{
mHandDirectedMovement = Settings::Manager::getBool("hand directed movement", "VR");
}
mHandDirectedMovement = Settings::Manager::getBool("hand directed movement", "VR");
setSeatedPlay(Settings::Manager::getBool("seated play", "VR"));
}
}
@ -98,6 +97,15 @@ namespace MWVR
}
}
void VRSession::setSeatedPlay(bool seatedPlay)
{
std::swap(mSeatedPlay, seatedPlay);
if (mSeatedPlay != seatedPlay)
{
Environment::get().getInputManager()->requestRecenter(true);
}
}
osg::Matrix VRSession::viewMatrix(osg::Vec3 position, osg::Quat orientation)
{
position = position * Constants::UnitsPerMeter;

View file

@ -66,9 +66,13 @@ namespace MWVR
void beginPhase(FramePhase phase);
std::unique_ptr<VRFrameMeta>& getFrame(FramePhase phase);
bool seatedPlay() const { return mSeatedPlay; }
float playerScale() const { return mPlayerScale; }
float setPlayerScale(float scale) { return mPlayerScale = scale; }
void setPlayerScale(float scale) { mPlayerScale = scale; }
float eyeLevel() const { return mEyeLevel; }
void setEyeLevel(float eyeLevel) { mEyeLevel = eyeLevel; }
osg::Matrix viewMatrix(osg::Vec3 position, osg::Quat orientation);
osg::Matrix viewMatrix(FramePhase phase, Side side, bool offset, bool glConvention);
@ -81,11 +85,15 @@ namespace MWVR
void beginFrame();
void endFrame();
protected:
void setSeatedPlay(bool seatedPlay);
private:
std::mutex mMutex{};
std::condition_variable mCondition{};
bool mHandDirectedMovement{ false };
bool mSeatedPlay{ false };
long long mFrames{ 0 };
long long mLastRenderedFrame{ 0 };
long long mLastPredictedDisplayTime{ 0 };
@ -95,6 +103,7 @@ namespace MWVR
std::chrono::steady_clock::time_point mLastRenderedFrameTimestamp{ std::chrono::steady_clock::now() };
float mPlayerScale{ 1.f };
float mEyeLevel{ 1.f };
};
}

View file

@ -964,7 +964,7 @@ namespace MWWorld
#ifdef USE_OPENXR
auto* xrInput = MWVR::Environment::get().getInputManager();
if (xrInput)
xrInput->requestRecenter();
xrInput->requestRecenter(false);
#endif
}
@ -987,7 +987,7 @@ namespace MWWorld
#ifdef USE_OPENXR
auto* xrInput = MWVR::Environment::get().getInputManager();
if (xrInput)
xrInput->requestRecenter();
xrInput->requestRecenter(false);
#endif
}

View file

@ -595,7 +595,17 @@
<Property key="Caption" value="Hand directed movement"/>
</Widget>
</Widget>
<Widget type="HBox" skin="" position="4 154 350 24">
<Widget type="HBox" skin="" position="4 154 260 24">
<Widget type="AutoSizedButton" skin="MW_Button" position="0 0 24 24" align="Left Top">
<UserString key="SettingCategory" value="VR"/>
<UserString key="SettingName" value="seated play"/>
<UserString key="SettingType" value="CheckButton"/>
</Widget>
<Widget type="AutoSizedTextBox" skin="SandText" position="28 4 71 16" align="Left Top">
<Property key="Caption" value="Seated play"/>
</Widget>
</Widget>
<Widget type="HBox" skin="" position="4 184 350 24">
<Widget type="ComboBox" skin="MW_ComboBox" position="0 0 85 24" align="Left Top" name="VRLeftHudPosition">
<Property key="AddItem" value="Wrist"/>
<Property key="AddItem" value="Top"/>

View file

@ -12,6 +12,11 @@
<Property key="FontHeight" value="32"/>
</Widget>
<Widget type="AutoSizedButton" skin="MW_Button" name="recenter" align="HCenter">
<Property key="Caption" value="Recenter"/>
<Property key="FontHeight" value="32"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="Widget" skin="IB_T" align="HCenter" position="0 0 125 4">
<Widget type="Widget"/>

View file

@ -1023,6 +1023,8 @@ left hand hud position = wrist
# As the general quality of OpenXR DirectX runtimes is better than OpenGL runtimes, i default this to true.
Prefer DirectX swapchains = true
seated play = false
[VR Debug]
# If true, OpenMW-VR will enable gamma postprocessing
gamma postprocessing = true