2020-10-17 10:33:46 +00:00
|
|
|
#include "vrsession.hpp"
|
|
|
|
#include "vrgui.hpp"
|
2020-05-24 16:00:42 +00:00
|
|
|
#include "vrenvironment.hpp"
|
2020-06-21 21:40:07 +00:00
|
|
|
#include "vrinputmanager.hpp"
|
2020-05-24 16:00:42 +00:00
|
|
|
#include "openxrmanager.hpp"
|
|
|
|
#include "openxrswapchain.hpp"
|
|
|
|
#include "../mwinput/inputmanagerimp.hpp"
|
|
|
|
#include "../mwbase/environment.hpp"
|
2020-06-07 18:02:03 +00:00
|
|
|
#include "../mwbase/statemanager.hpp"
|
2020-05-24 16:00:42 +00:00
|
|
|
|
|
|
|
#include <components/debug/debuglog.hpp>
|
|
|
|
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
2020-06-28 09:33:01 +00:00
|
|
|
#include <components/misc/stringops.hpp>
|
2020-05-24 16:00:42 +00:00
|
|
|
|
|
|
|
#include <osg/Camera>
|
|
|
|
|
2020-07-21 12:36:11 +00:00
|
|
|
#include <algorithm>
|
2020-05-24 16:00:42 +00:00
|
|
|
#include <vector>
|
|
|
|
#include <array>
|
|
|
|
#include <iostream>
|
|
|
|
#include <time.h>
|
2020-06-03 17:46:20 +00:00
|
|
|
#include <thread>
|
2020-05-24 16:00:42 +00:00
|
|
|
|
2020-06-07 18:02:03 +00:00
|
|
|
#ifdef max
|
|
|
|
#undef max
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef min
|
|
|
|
#undef min
|
|
|
|
#endif
|
|
|
|
|
2020-05-24 16:00:42 +00:00
|
|
|
namespace MWVR
|
|
|
|
{
|
2020-10-21 19:22:38 +00:00
|
|
|
static void swapConvention(osg::Vec3& v3)
|
|
|
|
{
|
|
|
|
|
|
|
|
float y = v3.y();
|
|
|
|
float z = v3.z();
|
|
|
|
v3.y() = z;
|
|
|
|
v3.z() = -y;
|
|
|
|
}
|
|
|
|
static void swapConvention(osg::Quat& q)
|
|
|
|
{
|
|
|
|
float y = q.y();
|
|
|
|
float z = q.z();
|
|
|
|
q.y() = z;
|
|
|
|
q.z() = -y;
|
|
|
|
}
|
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
VRSession::VRSession()
|
2020-06-28 09:33:01 +00:00
|
|
|
: mXrSyncPhase{FramePhase::Cull}
|
2020-06-26 21:02:48 +00:00
|
|
|
{
|
2020-08-08 10:24:24 +00:00
|
|
|
mHandDirectedMovement = Settings::Manager::getBool("hand directed movement", "VR");
|
2020-10-19 16:21:05 +00:00
|
|
|
auto syncPhase = Settings::Manager::getString("openxr sync phase", "VR Debug");
|
2020-06-28 09:33:01 +00:00
|
|
|
syncPhase = Misc::StringUtils::lowerCase(syncPhase);
|
|
|
|
if (syncPhase == "update")
|
|
|
|
mXrSyncPhase = FramePhase::Update;
|
|
|
|
else if (syncPhase == "cull")
|
|
|
|
mXrSyncPhase = FramePhase::Cull;
|
|
|
|
else if (syncPhase == "draw")
|
|
|
|
mXrSyncPhase = FramePhase::Draw;
|
|
|
|
else if (syncPhase == "swap")
|
|
|
|
mXrSyncPhase = FramePhase::Swap;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Log(Debug::Verbose) << "Invalid openxr sync phase " << syncPhase << ", defaulting to cull";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Log(Debug::Verbose) << "Using openxr sync phase " << syncPhase;
|
2020-06-26 21:02:48 +00:00
|
|
|
}
|
2020-05-24 16:00:42 +00:00
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
VRSession::~VRSession()
|
|
|
|
{
|
|
|
|
}
|
2020-05-24 16:00:42 +00:00
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
osg::Matrix VRSession::projectionMatrix(FramePhase phase, Side side)
|
|
|
|
{
|
|
|
|
assert(((int)side) < 2);
|
|
|
|
auto fov = predictedPoses(VRSession::FramePhase::Update).view[(int)side].fov;
|
|
|
|
float near_ = Settings::Manager::getFloat("near clip", "Camera");
|
|
|
|
float far_ = Settings::Manager::getFloat("viewing distance", "Camera");
|
|
|
|
return fov.perspectiveMatrix(near_, far_);
|
|
|
|
}
|
2020-05-24 16:00:42 +00:00
|
|
|
|
2020-07-20 13:03:48 +00:00
|
|
|
osg::Matrix VRSession::viewMatrix(osg::Vec3 position, osg::Quat orientation)
|
|
|
|
{
|
|
|
|
position = position * Environment::get().unitsPerMeter();
|
|
|
|
|
2020-10-21 19:22:38 +00:00
|
|
|
swapConvention(position);
|
|
|
|
swapConvention(orientation);
|
2020-07-20 13:03:48 +00:00
|
|
|
|
|
|
|
osg::Matrix viewMatrix;
|
|
|
|
viewMatrix.setTrans(-position);
|
|
|
|
viewMatrix.postMultRotate(orientation.conj());
|
|
|
|
return viewMatrix;
|
|
|
|
}
|
|
|
|
|
2020-10-21 19:22:38 +00:00
|
|
|
osg::Matrix VRSession::viewMatrix(FramePhase phase, Side side, bool offset, bool glConvention)
|
2020-06-26 21:02:48 +00:00
|
|
|
{
|
2020-07-22 18:01:56 +00:00
|
|
|
if (offset)
|
|
|
|
{
|
|
|
|
MWVR::Pose pose = predictedPoses(phase).view[(int)side].pose;
|
2020-10-21 19:22:38 +00:00
|
|
|
auto position = pose.position * Environment::get().unitsPerMeter();
|
|
|
|
auto orientation = pose.orientation;
|
|
|
|
|
|
|
|
if (glConvention)
|
|
|
|
{
|
|
|
|
swapConvention(position);
|
|
|
|
swapConvention(orientation);
|
|
|
|
}
|
|
|
|
|
|
|
|
osg::Matrix viewMatrix;
|
|
|
|
viewMatrix.setTrans(-position);
|
|
|
|
viewMatrix.postMultRotate(orientation.conj());
|
|
|
|
return viewMatrix;
|
2020-07-22 18:01:56 +00:00
|
|
|
}
|
|
|
|
else
|
2020-06-26 21:02:48 +00:00
|
|
|
{
|
2020-07-22 18:01:56 +00:00
|
|
|
MWVR::Pose pose = predictedPoses(phase).eye[(int)side];
|
2020-06-26 21:02:48 +00:00
|
|
|
osg::Vec3 position = pose.position * Environment::get().unitsPerMeter();
|
|
|
|
osg::Quat orientation = pose.orientation;
|
2020-10-21 19:22:38 +00:00
|
|
|
osg::Vec3 forward = orientation * osg::Vec3(0, 1, 0);
|
|
|
|
osg::Vec3 up = orientation * osg::Vec3(0, 0, 1);
|
|
|
|
|
|
|
|
if (glConvention)
|
|
|
|
{
|
|
|
|
swapConvention(position);
|
|
|
|
swapConvention(orientation);
|
|
|
|
swapConvention(forward);
|
|
|
|
swapConvention(up);
|
|
|
|
}
|
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
osg::Matrix viewMatrix;
|
|
|
|
viewMatrix.makeLookAt(position, position + forward, up);
|
|
|
|
|
|
|
|
return viewMatrix;
|
|
|
|
}
|
|
|
|
}
|
2020-05-24 16:00:42 +00:00
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
void VRSession::swapBuffers(osg::GraphicsContext* gc, VRViewer& viewer)
|
|
|
|
{
|
|
|
|
auto* xr = Environment::get().getManager();
|
2020-05-24 16:00:42 +00:00
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
beginPhase(FramePhase::Swap);
|
2020-05-24 16:00:42 +00:00
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
auto* frameMeta = getFrame(FramePhase::Swap).get();
|
2020-07-21 10:28:39 +00:00
|
|
|
auto leftView = viewer.getView("LeftEye");
|
|
|
|
auto rightView = viewer.getView("RightEye");
|
2020-05-24 16:00:42 +00:00
|
|
|
|
2020-10-18 12:22:03 +00:00
|
|
|
if (frameMeta->mShouldSyncFrameLoop)
|
2020-06-26 21:02:48 +00:00
|
|
|
{
|
2020-10-18 12:22:03 +00:00
|
|
|
if (frameMeta->mShouldRender)
|
|
|
|
{
|
|
|
|
viewer.blitEyesToMirrorTexture(gc);
|
|
|
|
gc->swapBuffersImplementation();
|
|
|
|
leftView->swapBuffers(gc);
|
|
|
|
rightView->swapBuffers(gc);
|
|
|
|
std::array<CompositionLayerProjectionView, 2> layerStack{};
|
|
|
|
layerStack[(int)Side::LEFT_SIDE].swapchain = &leftView->swapchain();
|
|
|
|
layerStack[(int)Side::RIGHT_SIDE].swapchain = &rightView->swapchain();
|
|
|
|
layerStack[(int)Side::LEFT_SIDE].pose = frameMeta->mPredictedPoses.eye[(int)Side::LEFT_SIDE] / mPlayerScale;
|
|
|
|
layerStack[(int)Side::RIGHT_SIDE].pose = frameMeta->mPredictedPoses.eye[(int)Side::RIGHT_SIDE] / mPlayerScale;
|
|
|
|
layerStack[(int)Side::LEFT_SIDE].fov = frameMeta->mPredictedPoses.view[(int)Side::LEFT_SIDE].fov;
|
|
|
|
layerStack[(int)Side::RIGHT_SIDE].fov = frameMeta->mPredictedPoses.view[(int)Side::RIGHT_SIDE].fov;
|
|
|
|
xr->endFrame(frameMeta->mFrameInfo, &layerStack);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gc->swapBuffersImplementation();
|
|
|
|
xr->endFrame(frameMeta->mFrameInfo, nullptr);
|
|
|
|
}
|
|
|
|
|
2020-07-21 10:28:39 +00:00
|
|
|
xr->xrResourceReleased();
|
2020-06-26 21:02:48 +00:00
|
|
|
}
|
2020-06-21 21:40:07 +00:00
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lock(mMutex);
|
2020-08-02 10:34:46 +00:00
|
|
|
mLastRenderedFrame = frameMeta->mFrameNo;
|
2020-06-26 21:02:48 +00:00
|
|
|
getFrame(FramePhase::Swap) = nullptr;
|
|
|
|
}
|
|
|
|
mCondition.notify_one();
|
2020-05-24 16:00:42 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
void VRSession::beginPhase(FramePhase phase)
|
2020-06-03 17:46:20 +00:00
|
|
|
{
|
2020-06-26 21:02:48 +00:00
|
|
|
Log(Debug::Debug) << "beginPhase(" << ((int)phase) << ") " << std::this_thread::get_id();
|
2020-06-07 18:02:03 +00:00
|
|
|
|
2020-08-02 10:34:46 +00:00
|
|
|
auto& frame = getFrame(phase);
|
|
|
|
if (frame)
|
2020-06-07 18:02:03 +00:00
|
|
|
{
|
2020-08-02 10:34:46 +00:00
|
|
|
// Happens once during startup but can be ignored that time.
|
|
|
|
// TODO: This issue would be cleaned up if beginPhase(Update) was called at a more appropriate location.
|
2020-06-26 21:02:48 +00:00
|
|
|
Log(Debug::Warning) << "advanceFramePhase called with a frame alreay in the target phase";
|
|
|
|
return;
|
2020-06-07 18:02:03 +00:00
|
|
|
}
|
2020-06-03 17:46:20 +00:00
|
|
|
|
2020-05-24 16:00:42 +00:00
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
if (phase == FramePhase::Update)
|
|
|
|
{
|
|
|
|
prepareFrame();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lock(mMutex);
|
|
|
|
FramePhase previousPhase = static_cast<FramePhase>((int)phase - 1);
|
|
|
|
if (!getFrame(previousPhase))
|
|
|
|
throw std::logic_error("beginPhase called without a frame");
|
2020-08-02 10:34:46 +00:00
|
|
|
frame = std::move(getFrame(previousPhase));
|
2020-07-25 10:28:52 +00:00
|
|
|
}
|
2020-06-07 18:02:03 +00:00
|
|
|
|
2020-10-18 12:22:03 +00:00
|
|
|
if (phase == mXrSyncPhase && frame->mShouldSyncFrameLoop)
|
2020-06-26 21:02:48 +00:00
|
|
|
{
|
2020-08-02 10:34:46 +00:00
|
|
|
// We may reach this point before xrEndFrame of the previous frame
|
|
|
|
// Must wait or openxr will interpret another call to xrBeginFrame() as skipping a frame
|
2020-06-26 21:02:48 +00:00
|
|
|
std::unique_lock<std::mutex> lock(mMutex);
|
|
|
|
while (mLastRenderedFrame != mFrames - 1)
|
|
|
|
{
|
|
|
|
mCondition.wait(lock);
|
|
|
|
}
|
2020-07-22 18:01:56 +00:00
|
|
|
|
2020-10-18 12:22:03 +00:00
|
|
|
Environment::get().getManager()->beginFrame();
|
2020-08-02 10:34:46 +00:00
|
|
|
}
|
2020-06-07 18:02:03 +00:00
|
|
|
}
|
2020-06-26 21:02:48 +00:00
|
|
|
|
|
|
|
std::unique_ptr<VRSession::VRFrameMeta>& VRSession::getFrame(FramePhase phase)
|
2020-06-03 17:46:20 +00:00
|
|
|
{
|
2020-06-26 21:02:48 +00:00
|
|
|
if ((unsigned int)phase >= mFrame.size())
|
|
|
|
throw std::logic_error("Invalid frame phase");
|
|
|
|
return mFrame[(int)phase];
|
2020-06-03 17:46:20 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
void VRSession::prepareFrame()
|
2020-06-03 17:46:20 +00:00
|
|
|
{
|
2020-06-26 21:02:48 +00:00
|
|
|
mFrames++;
|
|
|
|
|
|
|
|
auto* xr = Environment::get().getManager();
|
|
|
|
xr->handleEvents();
|
2020-08-02 10:34:46 +00:00
|
|
|
auto& frame = getFrame(FramePhase::Update);
|
|
|
|
frame.reset(new VRFrameMeta);
|
2020-06-26 21:02:48 +00:00
|
|
|
|
2020-08-02 10:34:46 +00:00
|
|
|
frame->mFrameNo = mFrames;
|
2020-10-18 12:22:03 +00:00
|
|
|
frame->mShouldSyncFrameLoop = xr->appShouldSyncFrameLoop();
|
|
|
|
frame->mShouldRender = xr->appShouldRender();
|
|
|
|
if (frame->mShouldSyncFrameLoop)
|
2020-06-03 17:46:20 +00:00
|
|
|
{
|
2020-08-02 10:34:46 +00:00
|
|
|
frame->mFrameInfo = xr->waitFrame();
|
|
|
|
xr->xrResourceAcquired();
|
2020-06-26 21:02:48 +00:00
|
|
|
|
2020-10-18 12:22:03 +00:00
|
|
|
if (frame->mShouldRender)
|
|
|
|
{
|
|
|
|
frame->mPredictedDisplayTime = frame->mFrameInfo.runtimePredictedDisplayTime;
|
|
|
|
|
|
|
|
PoseSet predictedPoses{};
|
|
|
|
|
|
|
|
xr->enablePredictions();
|
|
|
|
predictedPoses.head = xr->getPredictedHeadPose(frame->mPredictedDisplayTime, ReferenceSpace::STAGE) * mPlayerScale;
|
|
|
|
auto hmdViews = xr->getPredictedViews(frame->mPredictedDisplayTime, ReferenceSpace::VIEW);
|
|
|
|
predictedPoses.view[(int)Side::LEFT_SIDE].pose = hmdViews[(int)Side::LEFT_SIDE].pose * mPlayerScale;
|
|
|
|
predictedPoses.view[(int)Side::RIGHT_SIDE].pose = hmdViews[(int)Side::RIGHT_SIDE].pose * mPlayerScale;
|
|
|
|
predictedPoses.view[(int)Side::LEFT_SIDE].fov = hmdViews[(int)Side::LEFT_SIDE].fov;
|
|
|
|
predictedPoses.view[(int)Side::RIGHT_SIDE].fov = hmdViews[(int)Side::RIGHT_SIDE].fov;
|
|
|
|
auto stageViews = xr->getPredictedViews(frame->mPredictedDisplayTime, ReferenceSpace::STAGE);
|
|
|
|
predictedPoses.eye[(int)Side::LEFT_SIDE] = stageViews[(int)Side::LEFT_SIDE].pose * mPlayerScale;
|
|
|
|
predictedPoses.eye[(int)Side::RIGHT_SIDE] = stageViews[(int)Side::RIGHT_SIDE].pose * mPlayerScale;
|
|
|
|
|
|
|
|
auto* input = Environment::get().getInputManager();
|
|
|
|
if (input)
|
|
|
|
{
|
|
|
|
predictedPoses.hands[(int)Side::LEFT_SIDE] = input->getLimbPose(frame->mPredictedDisplayTime, TrackedLimb::LEFT_HAND) * mPlayerScale;
|
|
|
|
predictedPoses.hands[(int)Side::RIGHT_SIDE] = input->getLimbPose(frame->mPredictedDisplayTime, TrackedLimb::RIGHT_HAND) * mPlayerScale;
|
|
|
|
}
|
|
|
|
xr->disablePredictions();
|
|
|
|
|
|
|
|
frame->mPredictedPoses = predictedPoses;
|
|
|
|
}
|
|
|
|
}
|
2020-06-03 17:46:20 +00:00
|
|
|
}
|
2020-05-24 16:00:42 +00:00
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
const PoseSet& VRSession::predictedPoses(FramePhase phase)
|
|
|
|
{
|
|
|
|
auto& frame = getFrame(phase);
|
2020-06-03 17:46:20 +00:00
|
|
|
|
2020-10-18 12:22:03 +00:00
|
|
|
// TODO: Manage execution order properly instead of this hack
|
2020-06-26 21:02:48 +00:00
|
|
|
if (phase == FramePhase::Update && !frame)
|
|
|
|
beginPhase(FramePhase::Update);
|
2020-05-24 16:00:42 +00:00
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
if (!frame)
|
|
|
|
throw std::logic_error("Attempted to get poses from a phase with no current pose");
|
|
|
|
return frame->mPredictedPoses;
|
|
|
|
}
|
2020-05-24 16:00:42 +00:00
|
|
|
|
2020-06-26 21:02:48 +00:00
|
|
|
// 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
|
|
|
|
void getEulerAngles(const osg::Quat& quat, float& yaw, float& pitch, float& roll)
|
2020-05-24 16:00:42 +00:00
|
|
|
{
|
2020-06-26 21:02:48 +00:00
|
|
|
// Now do the computation
|
|
|
|
osg::Matrixd m2(osg::Matrixd::rotate(quat));
|
|
|
|
double* mat = (double*)m2.ptr();
|
|
|
|
double angle_x = 0.0;
|
|
|
|
double angle_y = 0.0;
|
|
|
|
double angle_z = 0.0;
|
|
|
|
double D, C, tr_x, tr_y;
|
|
|
|
angle_y = D = asin(mat[2]); /* Calculate Y-axis angle */
|
|
|
|
C = cos(angle_y);
|
|
|
|
if (fabs(C) > 0.005) /* Test for Gimball lock? */
|
|
|
|
{
|
|
|
|
tr_x = mat[10] / C; /* No, so get X-axis angle */
|
|
|
|
tr_y = -mat[6] / C;
|
|
|
|
angle_x = atan2(tr_y, tr_x);
|
|
|
|
tr_x = mat[0] / C; /* Get Z-axis angle */
|
|
|
|
tr_y = -mat[1] / C;
|
|
|
|
angle_z = atan2(tr_y, tr_x);
|
|
|
|
}
|
|
|
|
else /* Gimball lock has occurred */
|
|
|
|
{
|
|
|
|
angle_x = 0; /* Set X-axis angle to zero
|
|
|
|
*/
|
|
|
|
tr_x = mat[5]; /* And calculate Z-axis angle
|
|
|
|
*/
|
|
|
|
tr_y = mat[4];
|
|
|
|
angle_z = atan2(tr_y, tr_x);
|
|
|
|
}
|
|
|
|
|
|
|
|
yaw = angle_z;
|
|
|
|
pitch = angle_x;
|
|
|
|
roll = angle_y;
|
2020-05-24 16:00:42 +00:00
|
|
|
}
|
2020-06-26 21:02:48 +00:00
|
|
|
|
|
|
|
void VRSession::movementAngles(float& yaw, float& pitch)
|
2020-05-24 16:00:42 +00:00
|
|
|
{
|
2020-06-26 21:02:48 +00:00
|
|
|
if (!getFrame(FramePhase::Update))
|
|
|
|
beginPhase(FramePhase::Update);
|
2020-08-08 10:24:24 +00:00
|
|
|
if (mHandDirectedMovement)
|
|
|
|
{
|
|
|
|
auto frameMeta = getFrame(FramePhase::Update).get();
|
2020-06-26 21:02:48 +00:00
|
|
|
|
2020-08-08 10:24:24 +00:00
|
|
|
float headYaw = 0.f;
|
|
|
|
float headPitch = 0.f;
|
|
|
|
float headsWillRoll = 0.f;
|
2020-06-26 21:02:48 +00:00
|
|
|
|
2020-08-08 10:24:24 +00:00
|
|
|
float handYaw = 0.f;
|
|
|
|
float handPitch = 0.f;
|
|
|
|
float handRoll = 0.f;
|
2020-06-26 21:02:48 +00:00
|
|
|
|
2020-08-08 10:24:24 +00:00
|
|
|
getEulerAngles(frameMeta->mPredictedPoses.head.orientation, headYaw, headPitch, headsWillRoll);
|
|
|
|
getEulerAngles(frameMeta->mPredictedPoses.hands[(int)Side::LEFT_SIDE].orientation, handYaw, handPitch, handRoll);
|
2020-06-26 21:02:48 +00:00
|
|
|
|
2020-08-08 10:24:24 +00:00
|
|
|
yaw = handYaw - headYaw;
|
|
|
|
pitch = handPitch - headPitch;
|
|
|
|
}
|
2020-05-24 16:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|