Skip to content

Commit ad4ec15

Browse files
authored
Merge pull request #44 from ami-iit/test_projecion_quad
Fixed computation of projection matrix in case of asymmetric FOV
2 parents bc25a53 + caa1b1c commit ad4ec15

12 files changed

+130
-36
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ build/*
88
# Qtcreator
99
*.user*
1010
*.autosave
11+
12+
# Visual Studio
13+
.vs/
14+
CmakeSettings.json

CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ cmake_minimum_required(VERSION 3.12)
88

99
project(yarp-device-openxrheadset
1010
LANGUAGES C CXX
11-
VERSION 0.0.3)
11+
VERSION 0.0.4)
1212

1313
# Defines the CMAKE_INSTALL_LIBDIR, CMAKE_INSTALL_BINDIR and many other useful macros.
1414
# See https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html

src/devices/openxrheadset/OpenXrEigenConversions.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
#include <Eigen/Geometry>
1515

1616

17-
inline Eigen::Vector3f toEigen(const XrVector3f vector)
17+
inline Eigen::Vector3f toEigen(const XrVector3f &vector)
1818
{
1919
Eigen::Vector3f output;
2020
output[0] = vector.x;
@@ -23,7 +23,7 @@ inline Eigen::Vector3f toEigen(const XrVector3f vector)
2323
return output;
2424
}
2525

26-
inline Eigen::Quaternionf toEigen(const XrQuaternionf quaternion)
26+
inline Eigen::Quaternionf toEigen(const XrQuaternionf &quaternion)
2727
{
2828
Eigen::Quaternionf output;
2929
output.w() = quaternion.w;

src/devices/openxrheadset/OpenXrHeadset.cpp

+20-6
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,11 @@ bool yarp::dev::OpenXrHeadset::open(yarp::os::Searchable &cfg)
251251
double period = cfg.check("vr_period", yarp::os::Value(0.011)).asFloat64();
252252
this->setPeriod(period);
253253

254+
m_useNativeQuadLayers = cfg.check("use_native_quad_layers") && (cfg.find("use_native_quad_layers").isNull() || cfg.find("use_native_quad_layers").asBool());
254255

255256
m_openXrInterfaceSettings.posesPredictionInMs = cfg.check("vr_poses_prediction_in_ms", yarp::os::Value(0.0)).asFloat64();
256-
m_openXrInterfaceSettings.hideWindow = cfg.check("hide_window") && (cfg.find("hide_window").isNull() || cfg.find("hide_window").asBool());
257+
m_openXrInterfaceSettings.hideWindow = (m_useNativeQuadLayers && !cfg.check("hide_window")) || (cfg.check("hide_window") && (cfg.find("hide_window").isNull() || cfg.find("hide_window").asBool()));
258+
m_openXrInterfaceSettings.renderInPlaySpace = cfg.check("render_in_play_space") && (cfg.find("render_in_play_space").isNull() || cfg.find("render_in_play_space").asBool());
257259

258260
m_getStickAsAxis = cfg.check("stick_as_axis", yarp::os::Value(false)).asBool();
259261
m_rootFrame = cfg.check("tf_root_frame", yarp::os::Value("openxr_origin")).asString();
@@ -397,8 +399,20 @@ bool yarp::dev::OpenXrHeadset::threadInit()
397399
return false;
398400
}
399401

400-
m_eyesManager.options().leftEyeQuadLayer = m_openXrInterface.addHeadFixedOpenGLQuadLayer();
401-
m_eyesManager.options().rightEyeQuadLayer = m_openXrInterface.addHeadFixedOpenGLQuadLayer();
402+
auto getLayer = [this]() -> std::shared_ptr<IOpenXrQuadLayer>
403+
{
404+
if (m_useNativeQuadLayers)
405+
{
406+
return m_openXrInterface.addHeadFixedQuadLayer();
407+
}
408+
else
409+
{
410+
return m_openXrInterface.addHeadFixedOpenGLQuadLayer();
411+
}
412+
};
413+
414+
m_eyesManager.options().leftEyeQuadLayer = getLayer();
415+
m_eyesManager.options().rightEyeQuadLayer = getLayer();
402416

403417
if (!m_eyesManager.initialize())
404418
{
@@ -407,7 +421,7 @@ bool yarp::dev::OpenXrHeadset::threadInit()
407421

408422
for (GuiParam& gui : m_huds)
409423
{
410-
if (!gui.layer.initialize(m_openXrInterface.addHeadFixedOpenGLQuadLayer(), gui.portName)) {
424+
if (!gui.layer.initialize(getLayer(), gui.portName)) {
411425
yCError(OPENXRHEADSET) << "Cannot initialize" << gui.portName << "display texture.";
412426
return false;
413427
}
@@ -418,7 +432,7 @@ bool yarp::dev::OpenXrHeadset::threadInit()
418432

419433
for (LabelLayer& label : m_labels)
420434
{
421-
label.options.quadLayer = m_openXrInterface.addHeadFixedOpenGLQuadLayer();
435+
label.options.quadLayer = getLayer();
422436

423437
if (!label.layer.initialize(label.options)) {
424438
yCError(OPENXRHEADSET) << "Cannot initialize" << label.options.portName << "label.";
@@ -431,7 +445,7 @@ bool yarp::dev::OpenXrHeadset::threadInit()
431445

432446
for (SlideLayer& slide : m_slides)
433447
{
434-
slide.options.quadLayer = m_openXrInterface.addHeadFixedOpenGLQuadLayer();
448+
slide.options.quadLayer = getLayer();
435449
if (!slide.layer.initialize(slide.options)) {
436450
yCError(OPENXRHEADSET) << "Cannot initialize" << slide.options.portName << "slide.";
437451
return false;

src/devices/openxrheadset/OpenXrHeadset.h

+1
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ class yarp::dev::OpenXrHeadset : public yarp::dev::DeviceDriver,
317317
std::atomic_bool m_closed{ false };
318318

319319
OpenXrInterfaceSettings m_openXrInterfaceSettings;
320+
bool m_useNativeQuadLayers{ false };
320321
OpenXrInterface m_openXrInterface;
321322

322323
std::vector<bool> m_buttons;

src/devices/openxrheadset/OpenXrInterface.cpp

+44-13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <impl/OpenXrInterfaceImpl.h>
1010

1111
//#define DEBUG_RENDERING
12+
//#define DEBUG_RENDERING_LOCATION
1213

1314

1415
bool OpenXrInterface::checkExtensions()
@@ -504,8 +505,8 @@ bool OpenXrInterface::prepareXrCompositionLayers()
504505
m_pimpl->depth_projection_views[i].next = NULL;
505506
m_pimpl->depth_projection_views[i].minDepth = 0.f;
506507
m_pimpl->depth_projection_views[i].maxDepth = 1.f;
507-
m_pimpl->depth_projection_views[i].nearZ = 0.01f;
508-
m_pimpl->depth_projection_views[i].farZ = 100.0f;
508+
m_pimpl->depth_projection_views[i].nearZ = m_pimpl->nearZ;
509+
m_pimpl->depth_projection_views[i].farZ = m_pimpl->farZ;
509510

510511
m_pimpl->depth_projection_views[i].subImage.swapchain = m_pimpl->projection_view_depth_swapchains[i].swapchain;
511512
m_pimpl->depth_projection_views[i].subImage.imageArrayIndex = 0;
@@ -524,7 +525,7 @@ bool OpenXrInterface::prepareXrCompositionLayers()
524525
.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION,
525526
.next = NULL,
526527
.layerFlags = 0,
527-
.space = m_pimpl->view_space,
528+
.space = m_pimpl->renderInPlaySpace? m_pimpl->play_space : m_pimpl->view_space,
528529
.viewCount = static_cast<uint32_t>(m_pimpl->projection_views.size()),
529530
.views = m_pimpl->projection_views.data(),
530531
};
@@ -882,23 +883,37 @@ void OpenXrInterface::updateXrSpaces()
882883
.viewConfigurationType =
883884
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
884885
.displayTime = m_pimpl->frame_state.predictedDisplayTime,
885-
.space = m_pimpl->view_space};
886+
.space = m_pimpl->renderInPlaySpace ? m_pimpl->play_space : m_pimpl->view_space };
886887

887888

888-
uint32_t output_viewCount = m_pimpl->views.size();
889+
uint32_t output_viewCount = static_cast<uint32_t>(m_pimpl->views.size());
889890
XrResult result = xrLocateViews(m_pimpl->session, &view_locate_info, &(m_pimpl->view_state),
890-
m_pimpl->views.size(), &output_viewCount, m_pimpl->views.data());
891+
static_cast<uint32_t>(m_pimpl->views.size()), &output_viewCount, m_pimpl->views.data());
891892
if (!m_pimpl->checkXrOutput(result, "Failed to locate the views!"))
892893
{
893894
m_pimpl->view_state.viewStateFlags = 0; //Set to zero all the valid/tracked flags
894895
}
895896

896-
XrPosef identity_pose = { .orientation = {.x = 0, .y = 0, .z = 0, .w = 1.0},
897-
.position = {.x = 0, .y = 0, .z = 0} };
897+
m_pimpl->mid_views_pose = m_pimpl->views[0].pose;
898+
899+
for (size_t i = 1; i < m_pimpl->views.size(); ++i)
900+
{
901+
m_pimpl->mid_views_pose.position = toXr(Eigen::Vector3f(toEigen(m_pimpl->mid_views_pose.position) + toEigen(m_pimpl->views[i].pose.position)));
902+
}
903+
m_pimpl->mid_views_pose.position = toXr(Eigen::Vector3f(toEigen(m_pimpl->mid_views_pose.position)/static_cast<float>(m_pimpl->views.size())));
904+
905+
m_pimpl->mid_views_pose_inverted.orientation = toXr(toEigen(m_pimpl->mid_views_pose.orientation).inverse());
906+
m_pimpl->mid_views_pose_inverted.position = toXr(toEigen(m_pimpl->mid_views_pose_inverted.orientation) * -toEigen(m_pimpl->mid_views_pose.position));
898907

899908
for (size_t i = 0; i < m_pimpl->views.size(); ++i)
900909
{
910+
#ifdef DEBUG_RENDERING_LOCATION
911+
XrPosef identity_pose = {.orientation = {.x = 0, .y = 0, .z = 0, .w = 1.0},
912+
.position = {.x = 0, .y = 0, .z = 0}};
901913
m_pimpl->projection_views[i].pose = identity_pose;
914+
#else
915+
m_pimpl->projection_views[i].pose = m_pimpl->mid_views_pose;
916+
#endif
902917
m_pimpl->projection_views[i].fov = m_pimpl->views[i].fov;
903918
}
904919

@@ -1170,12 +1185,14 @@ void OpenXrInterface::render()
11701185
{
11711186
if (openGLLayer->shouldRender() && (openGLLayer->visibility() == IOpenXrQuadLayer::Visibility::LEFT_EYE || openGLLayer->visibility() == IOpenXrQuadLayer::Visibility::BOTH_EYES))
11721187
{
1173-
openGLLayer->setFOVs(std::abs(m_pimpl->views[0].fov.angleLeft) + std::abs(m_pimpl->views[0].fov.angleRight), std::abs(m_pimpl->views[0].fov.angleUp) + std::abs(m_pimpl->views[0].fov.angleDown));
1188+
openGLLayer->setFOVs(m_pimpl->views[0].fov);
1189+
openGLLayer->setDepthLimits(m_pimpl->nearZ, m_pimpl->farZ);
11741190
if (openGLLayer->visibility() == IOpenXrQuadLayer::Visibility::BOTH_EYES || !openGLLayer->offsetIsSet())
11751191
{
11761192
if (viewIsValid)
11771193
{
1178-
openGLLayer->setOffsetPosition(toEigen(m_pimpl->views[0].pose.position));
1194+
Eigen::Vector3f offset = toEigen(m_pimpl->mid_views_pose_inverted.orientation) * toEigen(m_pimpl->views[0].pose.position) + toEigen(m_pimpl->mid_views_pose_inverted.position);
1195+
openGLLayer->setOffsetPosition(offset);
11791196
}
11801197
else
11811198
{
@@ -1219,12 +1236,14 @@ void OpenXrInterface::render()
12191236
{
12201237
if (openGLLayer->shouldRender() && (openGLLayer->visibility() == IOpenXrQuadLayer::Visibility::RIGHT_EYE || openGLLayer->visibility() == IOpenXrQuadLayer::Visibility::BOTH_EYES))
12211238
{
1222-
openGLLayer->setFOVs(std::abs(m_pimpl->views[1].fov.angleLeft) + std::abs(m_pimpl->views[1].fov.angleRight), std::abs(m_pimpl->views[1].fov.angleUp) + std::abs(m_pimpl->views[1].fov.angleDown));
1239+
openGLLayer->setFOVs(m_pimpl->views[1].fov);
1240+
openGLLayer->setDepthLimits(m_pimpl->nearZ, m_pimpl->farZ);
12231241
if (openGLLayer->visibility() == IOpenXrQuadLayer::Visibility::BOTH_EYES || !openGLLayer->offsetIsSet())
12241242
{
12251243
if (viewIsValid)
12261244
{
1227-
openGLLayer->setOffsetPosition(toEigen(m_pimpl->views[1].pose.position));
1245+
Eigen::Vector3f offset = toEigen(m_pimpl->mid_views_pose_inverted.orientation) * toEigen(m_pimpl->views[1].pose.position) + toEigen(m_pimpl->mid_views_pose_inverted.position);
1246+
openGLLayer->setOffsetPosition(offset);
12281247
}
12291248
else
12301249
{
@@ -1293,6 +1312,12 @@ void OpenXrInterface::endXrFrame()
12931312
{
12941313
if (layer->shouldSubmit())
12951314
{
1315+
#ifdef DEBUG_RENDERING_LOCATION
1316+
layer->layer.pose = layer->desiredHeadFixedPose;
1317+
#else
1318+
layer->layer.pose = toXr(Eigen::Matrix4f(toEigen(m_pimpl->mid_views_pose) * toEigen(layer->desiredHeadFixedPose)));
1319+
#endif
1320+
12961321
m_pimpl->submitLayer((XrCompositionLayerBaseHeader*) &layer->layer);
12971322
}
12981323
}
@@ -1339,6 +1364,12 @@ bool OpenXrInterface::initialize(const OpenXrInterfaceSettings &settings)
13391364
m_pimpl->locate_space_prediction_in_ns = static_cast<long>(std::round(settings.posesPredictionInMs * 1e6));
13401365

13411366
m_pimpl->hideWindow = settings.hideWindow;
1367+
m_pimpl->renderInPlaySpace = settings.renderInPlaySpace;
1368+
1369+
#ifdef DEBUG_RENDERING_LOCATION
1370+
m_pimpl->renderInPlaySpace = true;
1371+
#endif // DEBUG_RENDERING_LOCATION
1372+
13421373

13431374
m_pimpl->closing = false;
13441375
m_pimpl->closed = false;
@@ -1442,7 +1473,7 @@ std::shared_ptr<IOpenXrQuadLayer> OpenXrInterface::addHeadFixedQuadLayer()
14421473
.type = XR_TYPE_COMPOSITION_LAYER_QUAD,
14431474
.next = NULL,
14441475
.layerFlags = 0,
1445-
.space = m_pimpl->view_space, //Head fixed
1476+
.space = m_pimpl->renderInPlaySpace ? m_pimpl->play_space : m_pimpl->view_space,
14461477
.eyeVisibility = XR_EYE_VISIBILITY_BOTH,
14471478
.subImage = {
14481479
.swapchain = XR_NULL_HANDLE,

src/devices/openxrheadset/OpenXrInterface.h

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ struct OpenXrInterfaceSettings
6565
{
6666
double posesPredictionInMs{0.0};
6767
bool hideWindow{false};
68+
bool renderInPlaySpace{false};
6869
};
6970

7071
class OpenXrInterface

src/devices/openxrheadset/impl/OpenGLQuadLayer.cpp

+37-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ bool OpenGLQuadLayer::initialize(int32_t imageMaxWidth, int32_t imageMaxHeight)
1818
m_imageMaxWidth = imageMaxWidth;
1919
m_imageMaxHeight = imageMaxHeight;
2020

21+
//Random initialization for FOV
22+
m_fov.angleLeft = -1.0f;
23+
m_fov.angleRight = 1.0f;
24+
m_fov.angleUp = 1.0f;
25+
m_fov.angleDown = -1.0f;
26+
2127
m_positions = {
2228
// vertex coords // texture coords
2329
-0.5f, -0.5f, 0.0, 1.0f, 0.0f, 0.0f, // 0 (the first 4 numbers of the row are the vertex coordinates, the second 2 numbers are the texture coordinate for that vertex (the bottom left corner of the rectangle is also the bottom left corner of the picture))
@@ -72,15 +78,22 @@ bool OpenGLQuadLayer::initialize(int32_t imageMaxWidth, int32_t imageMaxHeight)
7278
return true;
7379
}
7480

75-
void OpenGLQuadLayer::setFOVs(float fovX, float fovY)
81+
void OpenGLQuadLayer::setFOVs(const XrFovf& fov)
7682
{
77-
float tan_fovY_2 = std::tan(fovY/2);
83+
// Calculate horizontal and vertical FOVs
7884

79-
if (std::abs(tan_fovY_2) < 1e-15)
85+
if (std::abs(fov.angleRight - fov.angleLeft) < 1e-15)
86+
{
87+
yCError(OPENXRHEADSET) << "Horizontal FOV is zero.";
8088
return;
89+
}
8190

82-
m_fovY = fovY;
83-
m_aspectRatio = std::tan(fovX/2) / tan_fovY_2; //See https://en.wikipedia.org/wiki/Field_of_view_in_video_games
91+
if (std::abs(fov.angleUp - fov.angleDown) < 1e-15)
92+
{
93+
yCError(OPENXRHEADSET) << "Vertical FOV is zero.";
94+
return;
95+
}
96+
m_fov = fov;
8497
}
8598

8699
void OpenGLQuadLayer::setDepthLimits(float zNear, float zFar)
@@ -105,9 +118,26 @@ void OpenGLQuadLayer::render()
105118
glm::mat4 sca = glm::scale(glm::mat4(1.0f), m_modelScale);
106119

107120
glm::mat4 model = m_offsetTra * modelPose * sca;
108-
glm::mat4 proj = glm::perspective(m_fovY, m_aspectRatio, m_zNear, m_zFar); // 3D alternative to "ortho" proj type. It allows to define the view frustum by inserting the y FOV, the aspect ratio of the window, where are placed the near and far clipping planes
109121

110-
glm::mat4 layerTransform = proj * model;
122+
// Calculate perspective matrix
123+
float left = m_zNear * std::tan(m_fov.angleLeft);
124+
float right = m_zNear * std::tan(m_fov.angleRight);
125+
float bottom = m_zNear * std::tan(m_fov.angleDown);
126+
float top = m_zNear * std::tan(m_fov.angleUp);
127+
128+
glm::mat4 perspective_matrix = glm::mat4(0.0f);
129+
//The sintax for glm::mat4 is [col][row]
130+
//Source "Generalized Perspective Projection" By Robert Kooima.
131+
perspective_matrix[0][0] = (2.0f * m_zNear) / (right - left);
132+
perspective_matrix[1][1] = (2.0f * m_zNear) / (top - bottom);
133+
perspective_matrix[2][0] = (right + left) / (right - left);
134+
perspective_matrix[2][1] = (top + bottom) / (top - bottom);
135+
perspective_matrix[2][2] = -(m_zFar + m_zNear) / (m_zFar - m_zNear);
136+
perspective_matrix[2][3] = -1.0f;
137+
perspective_matrix[3][2] = -(2.0f * m_zFar * m_zNear) / (m_zFar - m_zNear);
138+
139+
140+
glm::mat4 layerTransform = perspective_matrix * model;
111141

112142
m_shader.bind(); // bind shader
113143
m_shader.setUniformMat4f("u_H", layerTransform);

src/devices/openxrheadset/impl/OpenGLQuadLayer.h

+2-3
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,9 @@ class OpenGLQuadLayer : public IOpenXrQuadLayer
5252

5353
glm::vec3 m_modelScale{1.0f, 1.0f, 1.0f};
5454

55-
float m_fovY = glm::radians(60.0f); // Field Of View
5655
float m_zNear = 0.1f;
5756
float m_zFar = 100.0f;
58-
float m_aspectRatio = 1.0f;
57+
XrFovf m_fov;
5958

6059
public:
6160

@@ -73,7 +72,7 @@ class OpenGLQuadLayer : public IOpenXrQuadLayer
7372

7473
bool initialize(int32_t imageMaxWidth, int32_t imageMaxHeight);
7574

76-
void setFOVs(float fovX, float fovY);
75+
void setFOVs(const XrFovf& fov);
7776

7877
void setDepthLimits(float zNear, float zFar);
7978

src/devices/openxrheadset/impl/OpenXrInterfaceImpl.h

+13
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,12 @@ class OpenXrInterface::Implementation
271271
// array of views, filled by the runtime with current HMD display pose (basically the position of each eye)
272272
std::vector<XrView> views;
273273

274+
// position of a frame in the middle of the eyes, oriented as the first eye
275+
XrPosef mid_views_pose;
276+
277+
// position of a frame in the middle of the eyes, oriented as the first eye
278+
XrPosef mid_views_pose_inverted;
279+
274280
// List of top level paths to retrieve the state of each action
275281
std::vector<TopLevelPath> top_level_paths;
276282

@@ -360,6 +366,13 @@ class OpenXrInterface::Implementation
360366

361367
// Flag to enable the window visualization
362368
bool hideWindow{false};
369+
370+
// Flag to enable the visualization of the layers in the play space instead of the head space
371+
bool renderInPlaySpace{false};
372+
373+
// Depth limits
374+
float nearZ = 0.01f;
375+
float farZ = 100.0f;
363376
};
364377

365378

0 commit comments

Comments
 (0)