1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-16 00:49:54 +00:00

In-place turning

This commit is contained in:
Mads Buvik Sandvei 2020-02-23 11:02:38 +01:00
parent 287886d545
commit bdade49129
16 changed files with 230 additions and 140 deletions

View file

@ -13,12 +13,13 @@ void OMW::Engine::initVr()
throw std::logic_error("mViewer must be initialized before calling initVr()"); throw std::logic_error("mViewer must be initialized before calling initVr()");
mXR = new MWVR::OpenXRManager(); mXR = new MWVR::OpenXRManager();
mEnvironment.setXRSession(new MWVR::OpenXRSession(mXR));
// Ref: https://wiki.openmw.org/index.php?title=Measurement_Units // Ref: https://wiki.openmw.org/index.php?title=Measurement_Units
float unitsPerYard = 64.f; float unitsPerYard = 64.f;
float yardsPerMeter = 0.9144f; float yardsPerMeter = 0.9144f;
float unitsPerMeter = unitsPerYard / yardsPerMeter; float unitsPerMeter = unitsPerYard / yardsPerMeter;
mXRViewer = new MWVR::OpenXRViewer(mXR, mViewer, unitsPerMeter); mEnvironment.setXRSession(new MWVR::OpenXRSession(mXR, unitsPerMeter));
mXRViewer = new MWVR::OpenXRViewer(mXR, mViewer);
} }

View file

@ -11,6 +11,10 @@
#include "../mwworld/ptr.hpp" #include "../mwworld/ptr.hpp"
#include "../mwworld/refdata.hpp" #include "../mwworld/refdata.hpp"
#ifdef USE_OPENXR
#include "../mwvr/openxrinputmanager.hpp"
#endif
#include "npcanimation.hpp" #include "npcanimation.hpp"
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
@ -63,9 +67,11 @@ namespace MWRender
mVanity.enabled = false; mVanity.enabled = false;
mVanity.allowed = true; mVanity.allowed = true;
mPreviewCam.roll = 0.f;
mPreviewCam.pitch = 0.f; mPreviewCam.pitch = 0.f;
mPreviewCam.yaw = 0.f; mPreviewCam.yaw = 0.f;
mPreviewCam.offset = 400.f; mPreviewCam.offset = 400.f;
mMainCam.roll = 0.f;
mMainCam.pitch = 0.f; mMainCam.pitch = 0.f;
mMainCam.yaw = 0.f; mMainCam.yaw = 0.f;
mMainCam.offset = 400.f; mMainCam.offset = 400.f;
@ -112,7 +118,17 @@ namespace MWRender
osg::Vec3d position = getFocalPoint(); 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); osg::Vec3d offset = orient * osg::Vec3d(0, isFirstPerson() ? 0 : -mCameraDistance, 0);
position += offset; position += offset;
@ -120,7 +136,10 @@ namespace MWRender
osg::Vec3d forward = orient * osg::Vec3d(0,1,0); osg::Vec3d forward = orient * osg::Vec3d(0,1,0);
osg::Vec3d up = orient * osg::Vec3d(0,0,1); 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() void Camera::reset()
@ -131,15 +150,17 @@ namespace MWRender
toggleViewMode(); toggleViewMode();
} }
void Camera::rotateCamera(float pitch, float yaw, bool adjust) void Camera::rotateCamera(float pitch, float roll, float yaw, bool adjust)
{ {
if (adjust) if (adjust)
{ {
pitch += getPitch(); pitch += getPitch();
yaw += getYaw(); yaw += getYaw();
roll += getRoll();
} }
setYaw(yaw); setYaw(yaw);
setPitch(pitch); setPitch(pitch);
setRoll(roll);
} }
void Camera::attachTo(const MWWorld::Ptr &ptr) void Camera::attachTo(const MWWorld::Ptr &ptr)
@ -175,7 +196,7 @@ namespace MWRender
if(mVanity.enabled) 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() float Camera::getPitch()
{ {
if (mVanity.enabled || mPreviewMode) { if (mVanity.enabled || mPreviewMode) {
@ -299,10 +343,10 @@ namespace MWRender
void Camera::setPitch(float angle) void Camera::setPitch(float angle)
{ {
#ifdef USE_OPENXR //#ifdef USE_OPENXR
// Pitch is defined purely by the HMD. // // Pitch is defined purely by the HMD.
return (void)angle; // return (void)angle;
#endif //#endif
const float epsilon = 0.000001f; const float epsilon = 0.000001f;
float limit = osg::PI_2 - epsilon; float limit = osg::PI_2 - epsilon;
if(mPreviewMode) if(mPreviewMode)

View file

@ -24,7 +24,7 @@ namespace MWRender
class Camera class Camera
{ {
struct CamData { struct CamData {
float pitch, yaw, offset; float roll, pitch, yaw, offset;
}; };
MWWorld::Ptr mTrackingPtr; MWWorld::Ptr mTrackingPtr;
@ -70,11 +70,14 @@ namespace MWRender
/// Set where the camera is looking at. Uses Morrowind (euler) angles /// Set where the camera is looking at. Uses Morrowind (euler) angles
/// \param rot Rotation angles in radians /// \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(); float getYaw();
void setYaw(float angle); void setYaw(float angle);
float getRoll();
void setRoll(float angle);
float getPitch(); float getPitch();
void setPitch(float angle); void setPitch(float angle);

View file

@ -696,7 +696,7 @@ namespace MWRender
if(ptr == mCamera->getTrackingPtr() && if(ptr == mCamera->getTrackingPtr() &&
!mCamera->isVanityOrPreviewModeEnabled()) !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); ptr.getRefData().getBaseNode()->setAttitude(rot);
@ -1305,7 +1305,7 @@ namespace MWRender
if(!mCamera->isVanityOrPreviewModeEnabled()) if(!mCamera->isVanityOrPreviewModeEnabled())
return false; return false;
mCamera->rotateCamera(rot[0], rot[2], true); mCamera->rotateCamera(rot[0], 0.f, rot[2], true);
return true; return true;
} }

View file

@ -109,7 +109,7 @@ void ForearmController::operator()(osg::Node* node, osg::NodeVisitor* nv)
// Get current world transform of limb // Get current world transform of limb
osg::Matrix worldToLimb = osg::computeLocalToWorld(node->getParentalNodePaths()[0]); osg::Matrix worldToLimb = osg::computeLocalToWorld(node->getParentalNodePaths()[0]);
// Get current world of the reference node // Get current world of the reference node
osg::Matrix worldReference = osg::computeLocalToWorld(mRelativeTo->getParentalNodePaths()[0]); osg::Matrix worldReference = osg::Matrix::identity();
// New transform is reference node + tracker. // New transform is reference node + tracker.
mTracker->computeLocalToWorldMatrix(worldReference, nullptr); mTracker->computeLocalToWorldMatrix(worldReference, nullptr);
// Get hand // Get hand

View file

@ -716,16 +716,16 @@ namespace MWVR
XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION }; XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION };
XrSpaceVelocity velocity{ XR_TYPE_SPACE_VELOCITY }; XrSpaceVelocity velocity{ XR_TYPE_SPACE_VELOCITY };
location.next = &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.position),
osg::fromXR(location.pose.orientation), osg::fromXR(location.pose.orientation),
osg::fromXR(velocity.linearVelocity) osg::fromXR(velocity.linearVelocity)
}; };
CHECK_XRCMD(xrLocateSpace(mHandSpace[(int)Chirality::RIGHT_HAND], referenceSpace, time, &location)); CHECK_XRCMD(xrLocateSpace(mHandSpace[(int)Side::RIGHT_HAND], referenceSpace, time, &location));
handPoses[(int)Chirality::RIGHT_HAND] = MWVR::Pose{ handPoses[(int)Side::RIGHT_HAND] = MWVR::Pose{
osg::fromXR(location.pose.position), osg::fromXR(location.pose.position),
osg::fromXR(location.pose.orientation), osg::fromXR(location.pose.orientation),
osg::fromXR(velocity.linearVelocity) osg::fromXR(velocity.linearVelocity)
@ -795,6 +795,8 @@ namespace MWVR
auto* session = MWBase::Environment::get().getXRSession(); auto* session = MWBase::Environment::get().getXRSession();
updateHead();
OpenXRActionEvent event{}; OpenXRActionEvent event{};
while (mXRInput->nextActionEvent(event)) while (mXRInput->nextActionEvent(event))
{ {
@ -936,7 +938,7 @@ namespace MWVR
} }
break; break;
case A_LookLeftRight: case A_LookLeftRight:
mInputBinder->getChannel(A_LookLeftRight)->setValue(event.value / 2.f + 0.5f); mYaw += osg::DegreesToRadians(event.value) * 2.f;
break; break;
case A_MoveLeftRight: case A_MoveLeftRight:
mInputBinder->getChannel(A_MoveLeftRight)->setValue(event.value / 2.f + 0.5f); 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; 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();
}
} }

View file

@ -35,6 +35,8 @@ namespace MWVR
/// Overriden to update XR inputs /// Overriden to update XR inputs
virtual void update(float dt, bool disableControls = false, bool disableEvents = false); virtual void update(float dt, bool disableControls = false, bool disableEvents = false);
void updateHead();
void processEvent(const OpenXRActionEvent& event); void processEvent(const OpenXRActionEvent& event);
PoseSet getHandPoses(int64_t time, TrackedSpace space); PoseSet getHandPoses(int64_t time, TrackedSpace space);
@ -43,6 +45,10 @@ namespace MWVR
osg::ref_ptr<OpenXRViewer> mXRViewer; osg::ref_ptr<OpenXRViewer> mXRViewer;
std::unique_ptr<OpenXRInput> mXRInput; std::unique_ptr<OpenXRInput> mXRInput;
Pose mPreviousHeadPose{};
osg::Vec3 mHeadOffset{ 0,0,0 };
bool mRecenter{ true };
float mYaw{ 0.f };
}; };
} }

View file

@ -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. 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, LEFT_HAND = 0,
RIGHT_HAND = 1 RIGHT_HAND = 1

View file

@ -520,21 +520,21 @@ namespace osg
Vec3 fromXR(XrVector3f v) Vec3 fromXR(XrVector3f v)
{ {
return Vec3{ v.x, v.y, v.z }; return Vec3{ v.x, -v.z, v.y };
} }
Quat fromXR(XrQuaternionf quat) 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) XrVector3f toXR(Vec3 v)
{ {
return XrVector3f{ v.x(), v.y(), v.z() }; return XrVector3f{ v.x(), v.z(), -v.y() };
} }
XrQuaternionf toXR(Quat quat) XrQuaternionf toXR(Quat quat)
{ {
return XrQuaternionf{ quat.x(), quat.y(), quat.z(), quat.w() }; return XrQuaternionf{ quat.x(), quat.z(), -quat.y(), quat.w() };
} }
} }

View file

@ -31,7 +31,7 @@ namespace MWVR
{ {
// I position menus half a meter in front of the player, facing the player. // I position menus half a meter in front of the player, facing the player.
mPose = predictedPose(); 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; mPose.orientation = -mPose.orientation;
Log(Debug::Verbose) << "Menu pose updated to: " << mPose; Log(Debug::Verbose) << "Menu pose updated to: " << mPose;

View file

@ -27,9 +27,10 @@
namespace MWVR namespace MWVR
{ {
OpenXRSession::OpenXRSession( OpenXRSession::OpenXRSession(
osg::ref_ptr<OpenXRManager> XR) osg::ref_ptr<OpenXRManager> XR,
float unitsPerMeter)
: mXR(XR) : mXR(XR)
// , mInputManager(new OpenXRInput(mXR)) , mUnitsPerMeter(unitsPerMeter)
{ {
} }
@ -67,31 +68,11 @@ namespace MWVR
if (!mXR->sessionRunning()) if (!mXR->sessionRunning())
return; 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"); Timer timer("OpenXRSession::waitFrame");
mXR->waitFrame(); mXR->waitFrame();
timer.checkpoint("waitFrame"); timer.checkpoint("waitFrame");
// mInputManager->updateControls();
predictNext(0); 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<OpenXRMenu*>(mLayerStack.layerObjects()[OpenXRLayerStack::MENU_VIEW_LAYER]);
// if (menuLayer)
// {
// menuLayer->setVisible(!menuLayer->isVisible());
// menuLayer->updatePosition();
// }
// }
//}
mPredictionsReady = true; 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 // 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)); osg::Matrixd m2(osg::Matrixd::rotate(quat));
double* mat = (double*)m2.ptr(); double* mat = (double*)m2.ptr();
double angle_x = 0.0; double angle_x = 0.0;
@ -152,16 +134,19 @@ namespace MWVR
angle_z = atan2(tr_y, tr_x); angle_z = atan2(tr_y, tr_x);
} }
return angle_z; yaw = angle_z;
pitch = angle_x;
roll = angle_y;
} }
float OpenXRSession::movementYaw(void) float OpenXRSession::movementYaw(void)
{ {
osg::Matrix lookAt; auto lhandquat = predictedPoses().hands[(int)TrackedSpace::VIEW][(int)MWVR::Side::LEFT_HAND].orientation;
lookAt.makeLookAt(osg::Vec3(0, 0, 0), osg::Vec3(0, 1, 0), osg::Vec3(0, 0, 1)); float yaw = 0.f;
lookAt = osg::Matrix::inverse(lookAt); float pitch = 0.f;
auto lhandquat = predictedPoses().hands[(int)MWVR::TrackedSpace::STAGE][(int)MWVR::Chirality::LEFT_HAND].orientation * lookAt.getRotate(); float roll = 0.f;
return getYaw(lhandquat); getEulerAngles(lhandquat, yaw, pitch, roll);
return yaw;
} }
void OpenXRSession::predictNext(int extraPeriods) void OpenXRSession::predictNext(int extraPeriods)
@ -170,6 +155,8 @@ namespace MWVR
auto input = MWBase::Environment::get().getXRInputManager(); auto input = MWBase::Environment::get().getXRInputManager();
auto previousHeadPose = mPredictedPoses.head[(int)TrackedSpace::STAGE];
// Update pose predictions // Update pose predictions
mPredictedPoses.head[(int)TrackedSpace::STAGE] = mXR->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::STAGE); 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); 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); mPredictedPoses.hands[(int)TrackedSpace::VIEW] = input->getHandPoses(mPredictedDisplayTime, TrackedSpace::VIEW);
auto stageViews = mXR->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::STAGE); auto stageViews = mXR->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::STAGE);
auto hmdViews = mXR->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::VIEW); 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::STAGE][(int)Side::LEFT_HAND] = fromXR(stageViews[(int)Side::LEFT_HAND].pose);
mPredictedPoses.eye[(int)TrackedSpace::VIEW][(int)Chirality::LEFT_HAND] = fromXR(hmdViews[(int)Chirality::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)Chirality::RIGHT_HAND] = fromXR(stageViews[(int)Chirality::RIGHT_HAND].pose); mPredictedPoses.eye[(int)TrackedSpace::STAGE][(int)Side::RIGHT_HAND] = fromXR(stageViews[(int)Side::RIGHT_HAND].pose);
mPredictedPoses.eye[(int)TrackedSpace::VIEW][(int)Chirality::RIGHT_HAND] = fromXR(hmdViews[(int)Chirality::RIGHT_HAND].pose); mPredictedPoses.eye[(int)TrackedSpace::VIEW][(int)Side::RIGHT_HAND] = fromXR(hmdViews[(int)Side::RIGHT_HAND].pose);
} }
} }

View file

@ -15,6 +15,8 @@
namespace MWVR namespace MWVR
{ {
extern void getEulerAngles(const osg::Quat& quat, float& yaw, float& pitch, float& roll);
class OpenXRSession class OpenXRSession
{ {
using seconds = std::chrono::duration<double>; using seconds = std::chrono::duration<double>;
@ -23,13 +25,13 @@ class OpenXRSession
using time_point = clock::time_point; using time_point = clock::time_point;
public: public:
OpenXRSession(osg::ref_ptr<OpenXRManager> XR); OpenXRSession(osg::ref_ptr<OpenXRManager> XR, float unitsPerMeter);
~OpenXRSession(); ~OpenXRSession();
void setLayer(OpenXRLayerStack::Layer layerType, OpenXRLayer* layer); void setLayer(OpenXRLayerStack::Layer layerType, OpenXRLayer* layer);
void swapBuffers(osg::GraphicsContext* gc); void swapBuffers(osg::GraphicsContext* gc);
PoseSets& predictedPoses() { return mPredictedPoses; }; const PoseSets& predictedPoses() const { return mPredictedPoses; };
//! Call before updating poses and other inputs //! Call before updating poses and other inputs
void waitFrame(); void waitFrame();
@ -41,14 +43,16 @@ public:
void updateMenuPosition(void); 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 movementYaw(void);
float unitsPerMeter() const { return mUnitsPerMeter; };
osg::ref_ptr<OpenXRManager> mXR; osg::ref_ptr<OpenXRManager> mXR;
OpenXRLayerStack mLayerStack{}; OpenXRLayerStack mLayerStack{};
float mUnitsPerMeter = 1.f;
PoseSets mPredictedPoses{}; PoseSets mPredictedPoses{};
bool mPredictionsReady{ false }; bool mPredictionsReady{ false };
}; };

View file

@ -1,6 +1,7 @@
#include "openxrviewer.hpp" #include "openxrviewer.hpp"
#include "openxrsession.hpp" #include "openxrsession.hpp"
#include "openxrmanagerimpl.hpp" #include "openxrmanagerimpl.hpp"
#include "openxrinputmanager.hpp"
#include "Windows.h" #include "Windows.h"
#include "../mwrender/vismask.hpp" #include "../mwrender/vismask.hpp"
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
@ -17,8 +18,7 @@ namespace MWVR
OpenXRViewer::OpenXRViewer( OpenXRViewer::OpenXRViewer(
osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<OpenXRManager> XR,
osg::ref_ptr<osgViewer::Viewer> viewer, osg::ref_ptr<osgViewer::Viewer> viewer)
float metersPerUnit)
: osg::Group() : osg::Group()
, mXR(XR) , mXR(XR)
, mCompositionLayerProjectionViews(2, {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW}) , mCompositionLayerProjectionViews(2, {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW})
@ -26,7 +26,6 @@ namespace MWVR
, mViewer(viewer) , mViewer(viewer)
, mPreDraw(new PredrawCallback(this)) , mPreDraw(new PredrawCallback(this))
, mPostDraw(new PostdrawCallback(this)) , mPostDraw(new PostdrawCallback(this))
, mMetersPerUnit(metersPerUnit)
, mConfigured(false) , mConfigured(false)
{ {
mViewer->setRealizeOperation(mRealizeOperation); mViewer->setRealizeOperation(mRealizeOperation);
@ -99,18 +98,19 @@ namespace MWVR
if (!mXR->realized()) if (!mXR->realized())
mXR->realize(context); mXR->realize(context);
auto* session = MWBase::Environment::get().getXRSession();
OpenXRSwapchain::Config leftConfig; OpenXRSwapchain::Config leftConfig;
leftConfig.width = mXR->impl().mConfigViews[(int)Chirality::LEFT_HAND].recommendedImageRectWidth; leftConfig.width = mXR->impl().mConfigViews[(int)Side::LEFT_HAND].recommendedImageRectWidth;
leftConfig.height = mXR->impl().mConfigViews[(int)Chirality::LEFT_HAND].recommendedImageRectHeight; leftConfig.height = mXR->impl().mConfigViews[(int)Side::LEFT_HAND].recommendedImageRectHeight;
leftConfig.samples = mXR->impl().mConfigViews[(int)Chirality::LEFT_HAND].recommendedSwapchainSampleCount; leftConfig.samples = mXR->impl().mConfigViews[(int)Side::LEFT_HAND].recommendedSwapchainSampleCount;
OpenXRSwapchain::Config rightConfig; OpenXRSwapchain::Config rightConfig;
rightConfig.width = mXR->impl().mConfigViews[(int)Chirality::RIGHT_HAND].recommendedImageRectWidth; rightConfig.width = mXR->impl().mConfigViews[(int)Side::RIGHT_HAND].recommendedImageRectWidth;
rightConfig.height = mXR->impl().mConfigViews[(int)Chirality::RIGHT_HAND].recommendedImageRectHeight; rightConfig.height = mXR->impl().mConfigViews[(int)Side::RIGHT_HAND].recommendedImageRectHeight;
rightConfig.samples = mXR->impl().mConfigViews[(int)Chirality::RIGHT_HAND].recommendedSwapchainSampleCount; rightConfig.samples = mXR->impl().mConfigViews[(int)Side::RIGHT_HAND].recommendedSwapchainSampleCount;
auto leftView = new OpenXRWorldView(mXR, "LeftEye", context->getState(), leftConfig, mMetersPerUnit); auto leftView = new OpenXRWorldView(mXR, "LeftEye", context->getState(), leftConfig, session->unitsPerMeter());
auto rightView = new OpenXRWorldView(mXR, "RightEye", context->getState(), rightConfig, mMetersPerUnit); auto rightView = new OpenXRWorldView(mXR, "RightEye", context->getState(), rightConfig, session->unitsPerMeter());
mViews["LeftEye"] = leftView; mViews["LeftEye"] = leftView;
mViews["RightEye"] = rightView; mViews["RightEye"] = rightView;
@ -160,7 +160,6 @@ namespace MWVR
menuCamera->setPreDrawCallback(mPreDraw); menuCamera->setPreDrawCallback(mPreDraw);
menuCamera->setPostDrawCallback(mPostDraw); menuCamera->setPostDrawCallback(mPostDraw);
auto* session = MWBase::Environment::get().getXRSession();
mViewer->addSlave(menuCamera, true); mViewer->addSlave(menuCamera, true);
mViewer->getSlave(0)._updateSlaveCallback = new OpenXRWorldView::UpdateSlaveCallback(mXR, session, leftView, context); mViewer->getSlave(0)._updateSlaveCallback = new OpenXRWorldView::UpdateSlaveCallback(mXR, session, leftView, context);
@ -214,20 +213,21 @@ namespace MWVR
if (!mConfigured) if (!mConfigured)
return; return;
////// NEW SYSTEM
Timer timer("OpenXRViewer::SwapBuffers"); 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"); timer.checkpoint("Views");
auto leftEyePose = toXR(mViews["LeftEye"]->predictedPose()); mCompositionLayerProjectionViews[0].pose = toXR(leftView->predictedPose());
auto rightEyePose = toXR(mViews["RightEye"]->predictedPose()); mCompositionLayerProjectionViews[1].pose = toXR(rightView->predictedPose());
mCompositionLayerProjectionViews[0].pose = leftEyePose;
mCompositionLayerProjectionViews[1].pose = rightEyePose;
timer.checkpoint("Poses"); timer.checkpoint("Poses");
// TODO: Keep track of these in the session too. // 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[0].fov = stageViews[0].fov;
mCompositionLayerProjectionViews[1].fov = stageViews[1].fov; mCompositionLayerProjectionViews[1].fov = stageViews[1].fov;
timer.checkpoint("Fovs"); timer.checkpoint("Fovs");
@ -237,7 +237,7 @@ namespace MWVR
{ {
mLayer.reset(new XrCompositionLayerProjection); mLayer.reset(new XrCompositionLayerProjection);
mLayer->type = XR_TYPE_COMPOSITION_LAYER_PROJECTION; mLayer->type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
mLayer->space = mXR->impl().mReferenceSpaceStage; mLayer->space = mXR->impl().mReferenceSpaceView;
mLayer->viewCount = 2; mLayer->viewCount = 2;
mLayer->views = mCompositionLayerProjectionViews.data(); mLayer->views = mCompositionLayerProjectionViews.data();
} }
@ -306,53 +306,64 @@ namespace MWVR
return; return;
} }
auto& poses = MWBase::Environment::get().getXRSession()->predictedPoses(); auto session = MWBase::Environment::get().getXRSession();
auto handPosesView = poses.hands[(int)TrackedSpace::VIEW]; auto& poses = session->predictedPoses();
auto handPosesStage = poses.hands[(int)TrackedSpace::STAGE]; 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") if (hand_transform->getName() == "tracker r hand")
chirality = (int)Chirality::RIGHT_HAND; {
side = (int)Side::RIGHT_HAND;
}
MWVR::Pose hand = handPosesStage[chirality]; MWVR::Pose handStage = handPosesStage[side];
mXR->playerScale(hand); MWVR::Pose headStage = poses.head[(int)TrackedSpace::STAGE];
auto orientation = hand.orientation; mXR->playerScale(handStage);
auto position = hand.position; mXR->playerScale(headStage);
position = position * mMetersPerUnit; 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. auto camera = mViewer->getCamera();
// 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). auto viewMatrix = camera->getViewMatrix();
// 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);
orientation = orientation * lookAt.getRotate();
//position = invViewMatrix.preMult(position); // Align orientation with the game world
position = lookAt.getRotate() * position; auto inputManager = MWBase::Environment::get().getXRInputManager();
if (inputManager)
// Morrowind's meshes do not point forward by default. {
// Static since they do not need to be recomputed. 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 float VRbias = osg::DegreesToRadians(-90.f);
static osg::Quat yaw(VRbias, osg::Vec3f(0, 1, 0)); static osg::Quat yaw(VRbias, osg::Vec3f(0, 0, 1));
static osg::Quat pitch(2.f * VRbias, osg::Vec3f(0, 0, 1)); static osg::Quat pitch(2.f * VRbias, osg::Vec3f(0, 1, 0));
static osg::Quat roll (-VRbias, osg::Vec3f(1, 0, 0)); static osg::Quat roll (2.f * VRbias, osg::Vec3f(1, 0, 0));
orientation = pitch * yaw * orientation; orientation = pitch * yaw * orientation;
if (hand_transform->getName() == "tracker r hand") if (hand_transform->getName() == "tracker r hand")
orientation = roll * orientation; orientation = roll * orientation;
else
orientation = roll.inverse() * orientation;
// Hand are by default not well-centered // 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); osg::Vec3 offcenter = osg::Vec3(-0.175, 0., .033);
if (hand_transform->getName() == "tracker r hand") if (hand_transform->getName() == "tracker r hand")
offcenter.z() *= -1.; offcenter.z() *= -1.;
osg::Vec3 recenter = orientation * offcenter; osg::Vec3 recenter = orientation * offcenter;
position = position + recenter * mMetersPerUnit; position = position + recenter * session->unitsPerMeter();
hand_transform->setAttitude(orientation); hand_transform->setAttitude(orientation);
hand_transform->setPosition(position); hand_transform->setPosition(position);

View file

@ -85,8 +85,7 @@ namespace MWVR
public: public:
OpenXRViewer( OpenXRViewer(
osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<OpenXRManager> XR,
osg::ref_ptr<osgViewer::Viewer> viewer, osg::ref_ptr<osgViewer::Viewer> viewer);
float metersPerUnit = 1.f);
~OpenXRViewer(void); ~OpenXRViewer(void);
@ -126,7 +125,6 @@ namespace MWVR
std::mutex mMutex; std::mutex mMutex;
float mMetersPerUnit = 1.f;
bool mConfigured = false; bool mConfigured = false;
}; };
} }

View file

@ -2,6 +2,7 @@
#include "openxrmanager.hpp" #include "openxrmanager.hpp"
#include "openxrmanagerimpl.hpp" #include "openxrmanagerimpl.hpp"
#include "../mwinput/inputmanagerimp.hpp" #include "../mwinput/inputmanagerimp.hpp"
#include "openxrinputmanager.hpp"
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/sdlutil/sdlgraphicswindow.hpp> #include <components/sdlutil/sdlgraphicswindow.hpp>
@ -11,6 +12,7 @@
#include <openxr/openxr.h> #include <openxr/openxr.h>
#include "../mwrender/vismask.hpp" #include "../mwrender/vismask.hpp"
#include "../mwbase/environment.hpp"
#include <osg/Camera> #include <osg/Camera>
#include <osgViewer/Renderer> #include <osgViewer/Renderer>
@ -83,7 +85,7 @@ namespace MWVR
auto hmdViews = mXR->impl().getPredictedViews(mXR->impl().frameState().predictedDisplayTime, TrackedSpace::VIEW); auto hmdViews = mXR->impl().getPredictedViews(mXR->impl().frameState().predictedDisplayTime, TrackedSpace::VIEW);
float near = Settings::Manager::getFloat("near clip", "Camera"); 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() //return perspectiveFovMatrix()
if(mName == "LeftEye") if(mName == "LeftEye")
return perspectiveFovMatrix(near, far, hmdViews[0].fov); return perspectiveFovMatrix(near, far, hmdViews[0].fov);
@ -94,15 +96,12 @@ namespace MWVR
{ {
MWVR::Pose pose = predictedPose(); MWVR::Pose pose = predictedPose();
mXR->playerScale(pose); mXR->playerScale(pose);
osg::Vec3 position = pose.position; osg::Vec3 position = pose.position * mUnitsPerMeter;
osg::Quat orientation = pose.orientation;
// 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::Matrix viewMatrix; osg::Matrix viewMatrix;
viewMatrix.setTrans(-position * mMetersPerUnit); viewMatrix.setTrans(-position);
viewMatrix.postMultRotate(pose.orientation.conj()); viewMatrix.postMultRotate(orientation.conj());
return viewMatrix; return viewMatrix;
} }
@ -112,9 +111,9 @@ namespace MWVR
std::string name, std::string name,
osg::ref_ptr<osg::State> state, osg::ref_ptr<osg::State> state,
OpenXRSwapchain::Config config, OpenXRSwapchain::Config config,
float metersPerUnit ) float unitsPerMeter)
: OpenXRView(XR, name, config, state) : OpenXRView(XR, name, config, state)
, mMetersPerUnit(metersPerUnit) , mUnitsPerMeter(unitsPerMeter)
{ {
} }
@ -140,9 +139,6 @@ namespace MWVR
auto* view = renderInfo.getView(); auto* view = renderInfo.getView();
auto* camera = renderInfo.getCurrentCamera(); auto* camera = renderInfo.getCurrentCamera();
auto name = camera->getName(); auto name = camera->getName();
//Log(Debug::Verbose) << "Updating camera " << name;
} }
void void
@ -160,23 +156,18 @@ namespace MWVR
{ {
mXR->handleEvents(); mXR->handleEvents();
mSession->waitFrame(); 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); mView->setPredictedPose(leftEyePose);
} }
else 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); mView->setPredictedPose(rightEyePose);
} }
if (!mXR->sessionRunning()) if (!mXR->sessionRunning())
return; return;
// TODO: This is where controls should update
auto viewMatrix = view.getCamera()->getViewMatrix() * mView->viewMatrix(); auto viewMatrix = view.getCamera()->getViewMatrix() * mView->viewMatrix();
//auto viewMatrix = mView->viewMatrix();
auto projectionMatrix = mView->projectionMatrix(); auto projectionMatrix = mView->projectionMatrix();
camera->setViewMatrix(viewMatrix); camera->setViewMatrix(viewMatrix);

View file

@ -26,7 +26,7 @@ namespace MWVR
}; };
public: public:
OpenXRWorldView(osg::ref_ptr<OpenXRManager> XR, std::string name, osg::ref_ptr<osg::State> state, OpenXRSwapchain::Config config, float metersPerUnit); OpenXRWorldView(osg::ref_ptr<OpenXRManager> XR, std::string name, osg::ref_ptr<osg::State> state, OpenXRSwapchain::Config config, float unitsPerMeter);
~OpenXRWorldView(); ~OpenXRWorldView();
//! Prepare for render (update matrices) //! Prepare for render (update matrices)
@ -36,7 +36,7 @@ namespace MWVR
//! View offset for this view //! View offset for this view
osg::Matrix viewMatrix(); osg::Matrix viewMatrix();
float mMetersPerUnit = 1.f; float mUnitsPerMeter = 1.f;
}; };
} }