diff --git a/apps/openmw/engine_vr.cpp b/apps/openmw/engine_vr.cpp index bbadfb789..3a3923fa9 100644 --- a/apps/openmw/engine_vr.cpp +++ b/apps/openmw/engine_vr.cpp @@ -13,12 +13,13 @@ void OMW::Engine::initVr() throw std::logic_error("mViewer must be initialized before calling initVr()"); mXR = new MWVR::OpenXRManager(); - mEnvironment.setXRSession(new MWVR::OpenXRSession(mXR)); // Ref: https://wiki.openmw.org/index.php?title=Measurement_Units float unitsPerYard = 64.f; float yardsPerMeter = 0.9144f; float unitsPerMeter = unitsPerYard / yardsPerMeter; - mXRViewer = new MWVR::OpenXRViewer(mXR, mViewer, unitsPerMeter); + mEnvironment.setXRSession(new MWVR::OpenXRSession(mXR, unitsPerMeter)); + + mXRViewer = new MWVR::OpenXRViewer(mXR, mViewer); } diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 1055016b3..592b1d997 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -11,6 +11,10 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/refdata.hpp" +#ifdef USE_OPENXR +#include "../mwvr/openxrinputmanager.hpp" +#endif + #include "npcanimation.hpp" #include @@ -63,9 +67,11 @@ namespace MWRender mVanity.enabled = false; mVanity.allowed = true; + mPreviewCam.roll = 0.f; mPreviewCam.pitch = 0.f; mPreviewCam.yaw = 0.f; mPreviewCam.offset = 400.f; + mMainCam.roll = 0.f; mMainCam.pitch = 0.f; mMainCam.yaw = 0.f; mMainCam.offset = 400.f; @@ -112,7 +118,17 @@ namespace MWRender osg::Vec3d position = getFocalPoint(); - osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); + +#ifdef USE_OPENXR + auto inputManager = MWBase::Environment::get().getXRInputManager(); + if (inputManager) + { + position += inputManager->mHeadOffset; + } +#endif + + osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getRoll(), osg::Vec3d(0, 1, 0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); + osg::Vec3d offset = orient * osg::Vec3d(0, isFirstPerson() ? 0 : -mCameraDistance, 0); position += offset; @@ -120,7 +136,10 @@ namespace MWRender osg::Vec3d forward = orient * osg::Vec3d(0,1,0); osg::Vec3d up = orient * osg::Vec3d(0,0,1); - cam->setViewMatrixAsLookAt(position, position + forward, up); + osg::Matrix lookAt = osg::Matrix::lookAt(position, position + forward, up); + + //cam->setViewMatrixAsLookAt(position, position + forward, up); + cam->setViewMatrix(lookAt); } void Camera::reset() @@ -131,15 +150,17 @@ namespace MWRender toggleViewMode(); } - void Camera::rotateCamera(float pitch, float yaw, bool adjust) + void Camera::rotateCamera(float pitch, float roll, float yaw, bool adjust) { if (adjust) { pitch += getPitch(); yaw += getYaw(); + roll += getRoll(); } setYaw(yaw); setPitch(pitch); + setRoll(roll); } void Camera::attachTo(const MWWorld::Ptr &ptr) @@ -175,7 +196,7 @@ namespace MWRender if(mVanity.enabled) { - rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true); + rotateCamera(0.f, 0.f, osg::DegreesToRadians(3.f * duration), true); } } @@ -289,6 +310,29 @@ namespace MWRender } } + float Camera::getRoll() + { + if (mVanity.enabled || mPreviewMode) + return mPreviewCam.roll; + return mMainCam.roll; + } + + void Camera::setRoll(float angle) + { + if (angle > osg::PI) { + angle -= osg::PI * 2; + } + else if (angle < -osg::PI) { + angle += osg::PI * 2; + } + if (mVanity.enabled || mPreviewMode) { + mPreviewCam.roll = angle; + } + else { + mMainCam.roll = angle; + } + } + float Camera::getPitch() { if (mVanity.enabled || mPreviewMode) { @@ -299,10 +343,10 @@ namespace MWRender void Camera::setPitch(float angle) { -#ifdef USE_OPENXR - // Pitch is defined purely by the HMD. - return (void)angle; -#endif +//#ifdef USE_OPENXR +// // Pitch is defined purely by the HMD. +// return (void)angle; +//#endif const float epsilon = 0.000001f; float limit = osg::PI_2 - epsilon; if(mPreviewMode) diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 573cf936f..2320b4552 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -24,7 +24,7 @@ namespace MWRender class Camera { struct CamData { - float pitch, yaw, offset; + float roll, pitch, yaw, offset; }; MWWorld::Ptr mTrackingPtr; @@ -70,11 +70,14 @@ namespace MWRender /// Set where the camera is looking at. Uses Morrowind (euler) angles /// \param rot Rotation angles in radians - void rotateCamera(float pitch, float yaw, bool adjust); + void rotateCamera(float pitch, float roll, float yaw, bool adjust); float getYaw(); void setYaw(float angle); + float getRoll(); + void setRoll(float angle); + float getPitch(); void setPitch(float angle); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 593881d87..aaef7dd8e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -696,7 +696,7 @@ namespace MWRender if(ptr == mCamera->getTrackingPtr() && !mCamera->isVanityOrPreviewModeEnabled()) { - mCamera->rotateCamera(-ptr.getRefData().getPosition().rot[0], -ptr.getRefData().getPosition().rot[2], false); + mCamera->rotateCamera(-ptr.getRefData().getPosition().rot[0], -ptr.getRefData().getPosition().rot[1], -ptr.getRefData().getPosition().rot[2], false); } ptr.getRefData().getBaseNode()->setAttitude(rot); @@ -1305,7 +1305,7 @@ namespace MWRender if(!mCamera->isVanityOrPreviewModeEnabled()) return false; - mCamera->rotateCamera(rot[0], rot[2], true); + mCamera->rotateCamera(rot[0], 0.f, rot[2], true); return true; } diff --git a/apps/openmw/mwvr/openxranimation.cpp b/apps/openmw/mwvr/openxranimation.cpp index bacad810a..b007558bd 100644 --- a/apps/openmw/mwvr/openxranimation.cpp +++ b/apps/openmw/mwvr/openxranimation.cpp @@ -109,7 +109,7 @@ void ForearmController::operator()(osg::Node* node, osg::NodeVisitor* nv) // 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::computeLocalToWorld(mRelativeTo->getParentalNodePaths()[0]); + osg::Matrix worldReference = osg::Matrix::identity(); // New transform is reference node + tracker. mTracker->computeLocalToWorldMatrix(worldReference, nullptr); // Get hand diff --git a/apps/openmw/mwvr/openxrinputmanager.cpp b/apps/openmw/mwvr/openxrinputmanager.cpp index 1d332ffb6..b0e87b9e8 100644 --- a/apps/openmw/mwvr/openxrinputmanager.cpp +++ b/apps/openmw/mwvr/openxrinputmanager.cpp @@ -716,16 +716,16 @@ namespace MWVR XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION }; XrSpaceVelocity velocity{ XR_TYPE_SPACE_VELOCITY }; location.next = &velocity; - CHECK_XRCMD(xrLocateSpace(mHandSpace[(int)Chirality::LEFT_HAND], referenceSpace, time, &location)); + CHECK_XRCMD(xrLocateSpace(mHandSpace[(int)Side::LEFT_HAND], referenceSpace, time, &location)); - handPoses[(int)Chirality::LEFT_HAND] = MWVR::Pose{ + handPoses[(int)Side::LEFT_HAND] = MWVR::Pose{ osg::fromXR(location.pose.position), osg::fromXR(location.pose.orientation), osg::fromXR(velocity.linearVelocity) }; - CHECK_XRCMD(xrLocateSpace(mHandSpace[(int)Chirality::RIGHT_HAND], referenceSpace, time, &location)); - handPoses[(int)Chirality::RIGHT_HAND] = MWVR::Pose{ + CHECK_XRCMD(xrLocateSpace(mHandSpace[(int)Side::RIGHT_HAND], referenceSpace, time, &location)); + handPoses[(int)Side::RIGHT_HAND] = MWVR::Pose{ osg::fromXR(location.pose.position), osg::fromXR(location.pose.orientation), osg::fromXR(velocity.linearVelocity) @@ -795,6 +795,8 @@ namespace MWVR auto* session = MWBase::Environment::get().getXRSession(); + updateHead(); + OpenXRActionEvent event{}; while (mXRInput->nextActionEvent(event)) { @@ -936,7 +938,7 @@ namespace MWVR } break; case A_LookLeftRight: - mInputBinder->getChannel(A_LookLeftRight)->setValue(event.value / 2.f + 0.5f); + mYaw += osg::DegreesToRadians(event.value) * 2.f; break; case A_MoveLeftRight: mInputBinder->getChannel(A_MoveLeftRight)->setValue(event.value / 2.f + 0.5f); @@ -957,4 +959,47 @@ namespace MWVR Log(Debug::Warning) << "Unhandled XR action " << event.action; } } + + void OpenXRInputManager::updateHead() + { + if (!mPlayer) + return; + + MWBase::World* world = MWBase::Environment::get().getWorld(); + auto player = mPlayer->getPlayer(); + + auto* session = MWBase::Environment::get().getXRSession(); + + auto currentHeadPose = session->predictedPoses().head[(int)TrackedSpace::STAGE]; + currentHeadPose.position *= session->unitsPerMeter(); + osg::Vec3 vrMovement = currentHeadPose.position - mPreviousHeadPose.position; + mPreviousHeadPose = currentHeadPose; + + float yaw = 0.f; + float pitch = 0.f; + float roll = 0.f; + getEulerAngles(currentHeadPose.orientation, yaw, pitch, roll); + + yaw += mYaw; + + if (mRecenter) + { + mHeadOffset = osg::Vec3(0, 0, 0); + mRecenter = false; + } + else + { + osg::Quat gameworldYaw = osg::Quat(mYaw, osg::Vec3(0, 0, -1)); + mHeadOffset += gameworldYaw * vrMovement; + + float rot[3]; + rot[0] = pitch; + rot[1] = roll; + rot[2] = yaw; + world->rotateObject(player, rot[0], rot[1], rot[2], MWBase::RotationFlag_none); + } + + // Z will and should not be caught by the characyer + mHeadOffset.z() = currentHeadPose.position.z(); + } } diff --git a/apps/openmw/mwvr/openxrinputmanager.hpp b/apps/openmw/mwvr/openxrinputmanager.hpp index 8e2ef5c8d..19815366a 100644 --- a/apps/openmw/mwvr/openxrinputmanager.hpp +++ b/apps/openmw/mwvr/openxrinputmanager.hpp @@ -35,6 +35,8 @@ namespace MWVR /// Overriden to update XR inputs virtual void update(float dt, bool disableControls = false, bool disableEvents = false); + void updateHead(); + void processEvent(const OpenXRActionEvent& event); PoseSet getHandPoses(int64_t time, TrackedSpace space); @@ -43,6 +45,10 @@ namespace MWVR osg::ref_ptr mXRViewer; std::unique_ptr mXRInput; + Pose mPreviousHeadPose{}; + osg::Vec3 mHeadOffset{ 0,0,0 }; + bool mRecenter{ true }; + float mYaw{ 0.f }; }; } diff --git a/apps/openmw/mwvr/openxrmanager.hpp b/apps/openmw/mwvr/openxrmanager.hpp index b7d9b38a6..79ea1bd55 100644 --- a/apps/openmw/mwvr/openxrmanager.hpp +++ b/apps/openmw/mwvr/openxrmanager.hpp @@ -70,7 +70,7 @@ namespace MWVR VIEW=1 //!< Track limb in the VR view space. Meaning a space with the head as origin and orientation. }; - enum class Chirality + enum class Side { LEFT_HAND = 0, RIGHT_HAND = 1 diff --git a/apps/openmw/mwvr/openxrmanagerimpl.cpp b/apps/openmw/mwvr/openxrmanagerimpl.cpp index 4245e4b92..2b41a806d 100644 --- a/apps/openmw/mwvr/openxrmanagerimpl.cpp +++ b/apps/openmw/mwvr/openxrmanagerimpl.cpp @@ -520,21 +520,21 @@ namespace osg Vec3 fromXR(XrVector3f v) { - return Vec3{ v.x, v.y, v.z }; + return Vec3{ v.x, -v.z, v.y }; } Quat fromXR(XrQuaternionf quat) { - return Quat{ quat.x, quat.y, quat.z, quat.w }; + return Quat{ quat.x, -quat.z, quat.y, quat.w }; } XrVector3f toXR(Vec3 v) { - return XrVector3f{ v.x(), v.y(), v.z() }; + return XrVector3f{ v.x(), v.z(), -v.y() }; } XrQuaternionf toXR(Quat quat) { - return XrQuaternionf{ quat.x(), quat.y(), quat.z(), quat.w() }; + return XrQuaternionf{ quat.x(), quat.z(), -quat.y(), quat.w() }; } } diff --git a/apps/openmw/mwvr/openxrmenu.cpp b/apps/openmw/mwvr/openxrmenu.cpp index 862373b5b..b0df080cb 100644 --- a/apps/openmw/mwvr/openxrmenu.cpp +++ b/apps/openmw/mwvr/openxrmenu.cpp @@ -31,7 +31,7 @@ namespace MWVR { // I position menus half a meter in front of the player, facing the player. mPose = predictedPose(); - mPose.position += mPose.orientation * osg::Vec3(0, 0, -0.5); + mPose.position += mPose.orientation * osg::Vec3(0, 0.5, 0); mPose.orientation = -mPose.orientation; Log(Debug::Verbose) << "Menu pose updated to: " << mPose; diff --git a/apps/openmw/mwvr/openxrsession.cpp b/apps/openmw/mwvr/openxrsession.cpp index d4ce5c395..8ba59a5f3 100644 --- a/apps/openmw/mwvr/openxrsession.cpp +++ b/apps/openmw/mwvr/openxrsession.cpp @@ -27,9 +27,10 @@ namespace MWVR { OpenXRSession::OpenXRSession( - osg::ref_ptr XR) + osg::ref_ptr XR, + float unitsPerMeter) : mXR(XR) - // , mInputManager(new OpenXRInput(mXR)) + , mUnitsPerMeter(unitsPerMeter) { } @@ -67,31 +68,11 @@ namespace MWVR if (!mXR->sessionRunning()) return; - // For now it seems we must just accept crap performance from the rendering loop - // Since Oculus' implementation of waitFrame() does not even attempt to reflect real - // render time and just incurs a huge useless delay. Timer timer("OpenXRSession::waitFrame"); mXR->waitFrame(); timer.checkpoint("waitFrame"); - // mInputManager->updateControls(); predictNext(0); - //OpenXRActionEvent event{}; - //while (mInputManager->nextActionEvent(event)) - //{ - // Log(Debug::Verbose) << "ActionEvent action=" << event.action << " onPress=" << event.onPress; - // if (event.action == MWInput::InputManager::A_GameMenu) - // { - // Log(Debug::Verbose) << "A_GameMenu"; - // auto* menuLayer = dynamic_cast(mLayerStack.layerObjects()[OpenXRLayerStack::MENU_VIEW_LAYER]); - // if (menuLayer) - // { - // menuLayer->setVisible(!menuLayer->isVisible()); - // menuLayer->updatePosition(); - // } - // } - //} - mPredictionsReady = true; } @@ -121,10 +102,11 @@ namespace MWVR } - // OSG doesn't provide API to extract yaw from a quat, but i need it. + // OSG doesn't provide API to extract euler angles from a quat, but i need it. // Credits goes to Dennis Bunfield, i just copied his formula https://narkive.com/v0re6547.4 - static float getYaw(const osg::Quat& quat) + void getEulerAngles(const osg::Quat& quat, float& yaw, float& pitch, float& roll) { + // Now do the computation osg::Matrixd m2(osg::Matrixd::rotate(quat)); double* mat = (double*)m2.ptr(); double angle_x = 0.0; @@ -152,16 +134,19 @@ namespace MWVR angle_z = atan2(tr_y, tr_x); } - return angle_z; + yaw = angle_z; + pitch = angle_x; + roll = angle_y; } float OpenXRSession::movementYaw(void) { - osg::Matrix lookAt; - lookAt.makeLookAt(osg::Vec3(0, 0, 0), osg::Vec3(0, 1, 0), osg::Vec3(0, 0, 1)); - lookAt = osg::Matrix::inverse(lookAt); - auto lhandquat = predictedPoses().hands[(int)MWVR::TrackedSpace::STAGE][(int)MWVR::Chirality::LEFT_HAND].orientation * lookAt.getRotate(); - return getYaw(lhandquat); + auto lhandquat = predictedPoses().hands[(int)TrackedSpace::VIEW][(int)MWVR::Side::LEFT_HAND].orientation; + float yaw = 0.f; + float pitch = 0.f; + float roll = 0.f; + getEulerAngles(lhandquat, yaw, pitch, roll); + return yaw; } void OpenXRSession::predictNext(int extraPeriods) @@ -170,6 +155,8 @@ namespace MWVR auto input = MWBase::Environment::get().getXRInputManager(); + auto previousHeadPose = mPredictedPoses.head[(int)TrackedSpace::STAGE]; + // Update pose predictions mPredictedPoses.head[(int)TrackedSpace::STAGE] = mXR->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::STAGE); mPredictedPoses.head[(int)TrackedSpace::VIEW] = mXR->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::VIEW); @@ -177,10 +164,10 @@ namespace MWVR mPredictedPoses.hands[(int)TrackedSpace::VIEW] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::VIEW); auto stageViews = mXR->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::STAGE); auto hmdViews = mXR->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::VIEW); - mPredictedPoses.eye[(int)TrackedSpace::STAGE][(int)Chirality::LEFT_HAND] = fromXR(stageViews[(int)Chirality::LEFT_HAND].pose); - mPredictedPoses.eye[(int)TrackedSpace::VIEW][(int)Chirality::LEFT_HAND] = fromXR(hmdViews[(int)Chirality::LEFT_HAND].pose); - mPredictedPoses.eye[(int)TrackedSpace::STAGE][(int)Chirality::RIGHT_HAND] = fromXR(stageViews[(int)Chirality::RIGHT_HAND].pose); - mPredictedPoses.eye[(int)TrackedSpace::VIEW][(int)Chirality::RIGHT_HAND] = fromXR(hmdViews[(int)Chirality::RIGHT_HAND].pose); + 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); } } diff --git a/apps/openmw/mwvr/openxrsession.hpp b/apps/openmw/mwvr/openxrsession.hpp index 189ba7945..7aa86e30e 100644 --- a/apps/openmw/mwvr/openxrsession.hpp +++ b/apps/openmw/mwvr/openxrsession.hpp @@ -15,6 +15,8 @@ namespace MWVR { +extern void getEulerAngles(const osg::Quat& quat, float& yaw, float& pitch, float& roll); + class OpenXRSession { using seconds = std::chrono::duration; @@ -23,13 +25,13 @@ class OpenXRSession using time_point = clock::time_point; public: - OpenXRSession(osg::ref_ptr XR); + OpenXRSession(osg::ref_ptr XR, float unitsPerMeter); ~OpenXRSession(); void setLayer(OpenXRLayerStack::Layer layerType, OpenXRLayer* layer); void swapBuffers(osg::GraphicsContext* gc); - PoseSets& predictedPoses() { return mPredictedPoses; }; + const PoseSets& predictedPoses() const { return mPredictedPoses; }; //! Call before updating poses and other inputs void waitFrame(); @@ -41,14 +43,16 @@ public: void updateMenuPosition(void); - //! Yaw the movement if a tracking limb is configured + //! Yaw angle to be used for offsetting movement direction float movementYaw(void); + float unitsPerMeter() const { return mUnitsPerMeter; }; + osg::ref_ptr mXR; OpenXRLayerStack mLayerStack{}; + float mUnitsPerMeter = 1.f; PoseSets mPredictedPoses{}; - bool mPredictionsReady{ false }; }; diff --git a/apps/openmw/mwvr/openxrviewer.cpp b/apps/openmw/mwvr/openxrviewer.cpp index 9ce2178ce..94c7dea71 100644 --- a/apps/openmw/mwvr/openxrviewer.cpp +++ b/apps/openmw/mwvr/openxrviewer.cpp @@ -1,6 +1,7 @@ #include "openxrviewer.hpp" #include "openxrsession.hpp" #include "openxrmanagerimpl.hpp" +#include "openxrinputmanager.hpp" #include "Windows.h" #include "../mwrender/vismask.hpp" #include "../mwmechanics/actorutil.hpp" @@ -17,8 +18,7 @@ namespace MWVR OpenXRViewer::OpenXRViewer( osg::ref_ptr XR, - osg::ref_ptr viewer, - float metersPerUnit) + osg::ref_ptr viewer) : osg::Group() , mXR(XR) , mCompositionLayerProjectionViews(2, {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW}) @@ -26,7 +26,6 @@ namespace MWVR , mViewer(viewer) , mPreDraw(new PredrawCallback(this)) , mPostDraw(new PostdrawCallback(this)) - , mMetersPerUnit(metersPerUnit) , mConfigured(false) { mViewer->setRealizeOperation(mRealizeOperation); @@ -99,18 +98,19 @@ namespace MWVR if (!mXR->realized()) mXR->realize(context); + auto* session = MWBase::Environment::get().getXRSession(); OpenXRSwapchain::Config leftConfig; - leftConfig.width = mXR->impl().mConfigViews[(int)Chirality::LEFT_HAND].recommendedImageRectWidth; - leftConfig.height = mXR->impl().mConfigViews[(int)Chirality::LEFT_HAND].recommendedImageRectHeight; - leftConfig.samples = mXR->impl().mConfigViews[(int)Chirality::LEFT_HAND].recommendedSwapchainSampleCount; + leftConfig.width = mXR->impl().mConfigViews[(int)Side::LEFT_HAND].recommendedImageRectWidth; + leftConfig.height = mXR->impl().mConfigViews[(int)Side::LEFT_HAND].recommendedImageRectHeight; + leftConfig.samples = mXR->impl().mConfigViews[(int)Side::LEFT_HAND].recommendedSwapchainSampleCount; OpenXRSwapchain::Config rightConfig; - rightConfig.width = mXR->impl().mConfigViews[(int)Chirality::RIGHT_HAND].recommendedImageRectWidth; - rightConfig.height = mXR->impl().mConfigViews[(int)Chirality::RIGHT_HAND].recommendedImageRectHeight; - rightConfig.samples = mXR->impl().mConfigViews[(int)Chirality::RIGHT_HAND].recommendedSwapchainSampleCount; + rightConfig.width = mXR->impl().mConfigViews[(int)Side::RIGHT_HAND].recommendedImageRectWidth; + rightConfig.height = mXR->impl().mConfigViews[(int)Side::RIGHT_HAND].recommendedImageRectHeight; + rightConfig.samples = mXR->impl().mConfigViews[(int)Side::RIGHT_HAND].recommendedSwapchainSampleCount; - auto leftView = new OpenXRWorldView(mXR, "LeftEye", context->getState(), leftConfig, mMetersPerUnit); - auto rightView = new OpenXRWorldView(mXR, "RightEye", context->getState(), rightConfig, mMetersPerUnit); + auto leftView = new OpenXRWorldView(mXR, "LeftEye", context->getState(), leftConfig, session->unitsPerMeter()); + auto rightView = new OpenXRWorldView(mXR, "RightEye", context->getState(), rightConfig, session->unitsPerMeter()); mViews["LeftEye"] = leftView; mViews["RightEye"] = rightView; @@ -160,7 +160,6 @@ namespace MWVR menuCamera->setPreDrawCallback(mPreDraw); menuCamera->setPostDrawCallback(mPostDraw); - auto* session = MWBase::Environment::get().getXRSession(); mViewer->addSlave(menuCamera, true); mViewer->getSlave(0)._updateSlaveCallback = new OpenXRWorldView::UpdateSlaveCallback(mXR, session, leftView, context); @@ -214,20 +213,21 @@ namespace MWVR if (!mConfigured) return; - ////// NEW SYSTEM Timer timer("OpenXRViewer::SwapBuffers"); - mViews["LeftEye"]->swapBuffers(gc); - mViews["RightEye"]->swapBuffers(gc); + + auto leftView = mViews["LeftEye"]; + auto rightView = mViews["RightEye"]; + + leftView->swapBuffers(gc); + rightView->swapBuffers(gc); timer.checkpoint("Views"); - auto leftEyePose = toXR(mViews["LeftEye"]->predictedPose()); - auto rightEyePose = toXR(mViews["RightEye"]->predictedPose()); - mCompositionLayerProjectionViews[0].pose = leftEyePose; - mCompositionLayerProjectionViews[1].pose = rightEyePose; + mCompositionLayerProjectionViews[0].pose = toXR(leftView->predictedPose()); + mCompositionLayerProjectionViews[1].pose = toXR(rightView->predictedPose()); timer.checkpoint("Poses"); // TODO: Keep track of these in the session too. - auto stageViews = mXR->impl().getPredictedViews(mXR->impl().frameState().predictedDisplayTime, TrackedSpace::STAGE); + auto stageViews = mXR->impl().getPredictedViews(mXR->impl().frameState().predictedDisplayTime, TrackedSpace::VIEW); mCompositionLayerProjectionViews[0].fov = stageViews[0].fov; mCompositionLayerProjectionViews[1].fov = stageViews[1].fov; timer.checkpoint("Fovs"); @@ -237,7 +237,7 @@ namespace MWVR { mLayer.reset(new XrCompositionLayerProjection); mLayer->type = XR_TYPE_COMPOSITION_LAYER_PROJECTION; - mLayer->space = mXR->impl().mReferenceSpaceStage; + mLayer->space = mXR->impl().mReferenceSpaceView; mLayer->viewCount = 2; mLayer->views = mCompositionLayerProjectionViews.data(); } @@ -306,53 +306,64 @@ namespace MWVR return; } - auto& poses = MWBase::Environment::get().getXRSession()->predictedPoses(); - auto handPosesView = poses.hands[(int)TrackedSpace::VIEW]; + auto session = MWBase::Environment::get().getXRSession(); + auto& poses = session->predictedPoses(); auto handPosesStage = poses.hands[(int)TrackedSpace::STAGE]; - int chirality = (int)Chirality::LEFT_HAND; + int side = (int)Side::LEFT_HAND; if (hand_transform->getName() == "tracker r hand") - chirality = (int)Chirality::RIGHT_HAND; + { + side = (int)Side::RIGHT_HAND; + } - MWVR::Pose hand = handPosesStage[chirality]; - mXR->playerScale(hand); - auto orientation = hand.orientation; - auto position = hand.position; - position = position * mMetersPerUnit; + MWVR::Pose handStage = handPosesStage[side]; + MWVR::Pose headStage = poses.head[(int)TrackedSpace::STAGE]; + mXR->playerScale(handStage); + mXR->playerScale(headStage); + auto orientation = handStage.orientation; + auto position = handStage.position - headStage.position; + position = position * session->unitsPerMeter(); - // Move OpenXR's poses into OpenMW's view by applying the inverse of the rotation of the view matrix. - // This works because OpenXR's conventions match opengl's clip space, thus the inverse of the view matrix converts an OpenXR pose to OpenMW's view space (including world rotation). - // For the hands we don't want the full camera view matrix, but the relative matrix from the player root. So i create a lookat matrix based on osg's conventions. - // TODO: The full camera view matrix could work if i change how animations are overriden. - osg::Matrix lookAt; - lookAt.makeLookAt(osg::Vec3(0, 0, 0), osg::Vec3(0, 1, 0), osg::Vec3(0, 0, 1)); - lookAt = osg::Matrix::inverse(lookAt); + auto camera = mViewer->getCamera(); + auto viewMatrix = camera->getViewMatrix(); - orientation = orientation * lookAt.getRotate(); - //position = invViewMatrix.preMult(position); - position = lookAt.getRotate() * position; - - // Morrowind's meshes do not point forward by default. - // Static since they do not need to be recomputed. + + // Align orientation with the game world + auto inputManager = MWBase::Environment::get().getXRInputManager(); + 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, 1, 0)); - static osg::Quat pitch(2.f * VRbias, osg::Vec3f(0, 0, 1)); - static osg::Quat roll (-VRbias, osg::Vec3f(1, 0, 0)); + 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; - else - orientation = roll.inverse() * orientation; // Hand are by default not well-centered - // Note, these numbers are just a rough guess, but seem to work out well. + // 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 * mMetersPerUnit; + position = position + recenter * session->unitsPerMeter(); hand_transform->setAttitude(orientation); hand_transform->setPosition(position); diff --git a/apps/openmw/mwvr/openxrviewer.hpp b/apps/openmw/mwvr/openxrviewer.hpp index 16890398f..315cbd15f 100644 --- a/apps/openmw/mwvr/openxrviewer.hpp +++ b/apps/openmw/mwvr/openxrviewer.hpp @@ -85,8 +85,7 @@ namespace MWVR public: OpenXRViewer( osg::ref_ptr XR, - osg::ref_ptr viewer, - float metersPerUnit = 1.f); + osg::ref_ptr viewer); ~OpenXRViewer(void); @@ -126,7 +125,6 @@ namespace MWVR std::mutex mMutex; - float mMetersPerUnit = 1.f; bool mConfigured = false; }; } diff --git a/apps/openmw/mwvr/openxrworldview.cpp b/apps/openmw/mwvr/openxrworldview.cpp index cccfc4913..d14f22751 100644 --- a/apps/openmw/mwvr/openxrworldview.cpp +++ b/apps/openmw/mwvr/openxrworldview.cpp @@ -2,6 +2,7 @@ #include "openxrmanager.hpp" #include "openxrmanagerimpl.hpp" #include "../mwinput/inputmanagerimp.hpp" +#include "openxrinputmanager.hpp" #include #include @@ -11,6 +12,7 @@ #include #include "../mwrender/vismask.hpp" +#include "../mwbase/environment.hpp" #include #include @@ -83,7 +85,7 @@ namespace MWVR auto hmdViews = mXR->impl().getPredictedViews(mXR->impl().frameState().predictedDisplayTime, TrackedSpace::VIEW); float near = Settings::Manager::getFloat("near clip", "Camera"); - float far = Settings::Manager::getFloat("viewing distance", "Camera") * mMetersPerUnit; + float far = Settings::Manager::getFloat("viewing distance", "Camera"); //return perspectiveFovMatrix() if(mName == "LeftEye") return perspectiveFovMatrix(near, far, hmdViews[0].fov); @@ -94,15 +96,12 @@ namespace MWVR { MWVR::Pose pose = predictedPose(); mXR->playerScale(pose); - osg::Vec3 position = pose.position; - - // invert orientation (co jugate of Quaternion) and position to apply to the view matrix as offset. - // This works, despite different conventions between OpenXR and OSG, because the OSG view matrix will - // have converted to OpenGL's clip space conventions before this matrix is applied, and OpenXR's conventions - // match OpenGL. + osg::Vec3 position = pose.position * mUnitsPerMeter; + osg::Quat orientation = pose.orientation; + osg::Matrix viewMatrix; - viewMatrix.setTrans(-position * mMetersPerUnit); - viewMatrix.postMultRotate(pose.orientation.conj()); + viewMatrix.setTrans(-position); + viewMatrix.postMultRotate(orientation.conj()); return viewMatrix; } @@ -112,9 +111,9 @@ namespace MWVR std::string name, osg::ref_ptr state, OpenXRSwapchain::Config config, - float metersPerUnit ) + float unitsPerMeter) : OpenXRView(XR, name, config, state) - , mMetersPerUnit(metersPerUnit) + , mUnitsPerMeter(unitsPerMeter) { } @@ -140,9 +139,6 @@ namespace MWVR auto* view = renderInfo.getView(); auto* camera = renderInfo.getCurrentCamera(); auto name = camera->getName(); - - //Log(Debug::Verbose) << "Updating camera " << name; - } void @@ -160,23 +156,18 @@ namespace MWVR { mXR->handleEvents(); mSession->waitFrame(); - auto leftEyePose = poses.eye[(int)TrackedSpace::STAGE][(int)Chirality::LEFT_HAND]; + auto leftEyePose = poses.eye[(int)TrackedSpace::VIEW][(int)Side::LEFT_HAND]; mView->setPredictedPose(leftEyePose); } else { - auto rightEyePose = poses.eye[(int)TrackedSpace::STAGE][(int)Chirality::RIGHT_HAND]; + auto rightEyePose = poses.eye[(int)TrackedSpace::VIEW][(int)Side::RIGHT_HAND]; mView->setPredictedPose(rightEyePose); } if (!mXR->sessionRunning()) return; - // TODO: This is where controls should update - - - auto viewMatrix = view.getCamera()->getViewMatrix() * mView->viewMatrix(); - //auto viewMatrix = mView->viewMatrix(); auto projectionMatrix = mView->projectionMatrix(); camera->setViewMatrix(viewMatrix); diff --git a/apps/openmw/mwvr/openxrworldview.hpp b/apps/openmw/mwvr/openxrworldview.hpp index 39070b20b..a590acb8e 100644 --- a/apps/openmw/mwvr/openxrworldview.hpp +++ b/apps/openmw/mwvr/openxrworldview.hpp @@ -26,7 +26,7 @@ namespace MWVR }; public: - OpenXRWorldView(osg::ref_ptr XR, std::string name, osg::ref_ptr state, OpenXRSwapchain::Config config, float metersPerUnit); + OpenXRWorldView(osg::ref_ptr XR, std::string name, osg::ref_ptr state, OpenXRSwapchain::Config config, float unitsPerMeter); ~OpenXRWorldView(); //! Prepare for render (update matrices) @@ -36,7 +36,7 @@ namespace MWVR //! View offset for this view osg::Matrix viewMatrix(); - float mMetersPerUnit = 1.f; + float mUnitsPerMeter = 1.f; }; }