Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenXR: Allow changing play area mode during active session #87745

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/classes/XRInterface.xml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
<param index="0" name="mode" type="int" enum="XRInterface.PlayAreaMode" />
<description>
Sets the active play area mode, will return [code]false[/code] if the mode can't be used with this interface.
[b]Note:[/b] Changing this after the interface has already been initialized can be jarring for the player, so it's recommended to recenter on the HMD with [method XRServer.center_on_hmd] (if switching to [constant XRInterface.XR_PLAY_AREA_STAGE]) or make the switch during a scene change.
</description>
</method>
<method name="start_passthrough">
Expand Down
6 changes: 6 additions & 0 deletions doc/classes/XRServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
You should call this method after a few seconds have passed. For example, when the user requests a realignment of the display holding a designated button on a controller for a short period of time, or when implementing a teleport mechanism.
</description>
</method>
<method name="clear_reference_frame" qualifiers="const">
<return type="Transform3D" />
<description>
Clears the reference frame that was set by previous calls to [method center_on_hmd].
</description>
</method>
<method name="find_interface" qualifiers="const">
<return type="XRInterface" />
<param index="0" name="name" type="String" />
Expand Down
127 changes: 75 additions & 52 deletions modules/openxr/openxr_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -680,71 +680,85 @@ bool OpenXRAPI::is_reference_space_supported(XrReferenceSpaceType p_reference_sp
return false;
}

bool OpenXRAPI::setup_spaces() {
XrResult result;
bool OpenXRAPI::setup_play_space() {
ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);

XrPosef identityPose = {
{ 0.0, 0.0, 0.0, 1.0 },
{ 0.0, 0.0, 0.0 }
};

ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
XrReferenceSpaceType new_reference_space;
XrSpace new_play_space = XR_NULL_HANDLE;
bool will_emulate_local_floor = false;

// create play space
{
emulating_local_floor = false;
if (is_reference_space_supported(requested_reference_space)) {
new_reference_space = requested_reference_space;
} else if (requested_reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT && is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_STAGE)) {
print_verbose("OpenXR: LOCAL_FLOOR space isn't supported, emulating using STAGE and LOCAL spaces.");

if (is_reference_space_supported(requested_reference_space)) {
reference_space = requested_reference_space;
} else if (requested_reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT && is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_STAGE)) {
print_verbose("OpenXR: LOCAL_FLOOR space isn't supported, emulating using STAGE and LOCAL spaces.");
new_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
will_emulate_local_floor = true;
} else {
// Fallback on LOCAL, which all OpenXR runtimes are required to support.
print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(requested_reference_space) + String(" isn't supported, defaulting to LOCAL space."));
new_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
}

reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
emulating_local_floor = true;
XrReferenceSpaceCreateInfo play_space_create_info = {
XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
nullptr, // next
new_reference_space, // referenceSpaceType
identityPose, // poseInReferenceSpace
};

// We'll use the STAGE space to get the floor height, but we can't do that until
// after xrWaitFrame(), so just set this flag for now.
should_reset_emulated_floor_height = true;
XrResult result = xrCreateReferenceSpace(session, &play_space_create_info, &new_play_space);
if (XR_FAILED(result)) {
print_line("OpenXR: Failed to create play space [", get_error_string(result), "]");
return false;
}

} else {
// Fallback on LOCAL, which all OpenXR runtimes are required to support.
print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(requested_reference_space) + String(" isn't supported, defaulting to LOCAL space."));
reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
}
// If we've previously created a play space, clean it up first.
if (play_space != XR_NULL_HANDLE) {
xrDestroySpace(play_space);
}
play_space = new_play_space;
reference_space = new_reference_space;

XrReferenceSpaceCreateInfo play_space_create_info = {
XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
nullptr, // next
reference_space, // referenceSpaceType
identityPose, // poseInReferenceSpace
};
emulating_local_floor = will_emulate_local_floor;
if (emulating_local_floor) {
// We'll use the STAGE space to get the floor height, but we can't do that until
// after xrWaitFrame(), so just set this flag for now.
should_reset_emulated_floor_height = true;
}

result = xrCreateReferenceSpace(session, &play_space_create_info, &play_space);
if (XR_FAILED(result)) {
print_line("OpenXR: Failed to create play space [", get_error_string(result), "]");
return false;
}
return true;
}

bool OpenXRAPI::setup_view_space() {
ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);

if (!is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_VIEW)) {
print_line("OpenXR: reference space XR_REFERENCE_SPACE_TYPE_VIEW is not supported.");
return false;
}

// create view space
{
if (!is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_VIEW)) {
print_line("OpenXR: reference space XR_REFERENCE_SPACE_TYPE_VIEW is not supported.");
return false;
}
XrPosef identityPose = {
{ 0.0, 0.0, 0.0, 1.0 },
{ 0.0, 0.0, 0.0 }
};

XrReferenceSpaceCreateInfo view_space_create_info = {
XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
nullptr, // next
XR_REFERENCE_SPACE_TYPE_VIEW, // referenceSpaceType
identityPose // poseInReferenceSpace
};
XrReferenceSpaceCreateInfo view_space_create_info = {
XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
nullptr, // next
XR_REFERENCE_SPACE_TYPE_VIEW, // referenceSpaceType
identityPose // poseInReferenceSpace
};

result = xrCreateReferenceSpace(session, &view_space_create_info, &view_space);
if (XR_FAILED(result)) {
print_line("OpenXR: Failed to create view space [", get_error_string(result), "]");
return false;
}
XrResult result = xrCreateReferenceSpace(session, &view_space_create_info, &view_space);
if (XR_FAILED(result)) {
print_line("OpenXR: Failed to create view space [", get_error_string(result), "]");
return false;
}

return true;
Expand Down Expand Up @@ -1262,10 +1276,14 @@ void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configurat
view_configuration = p_view_configuration;
}

void OpenXRAPI::set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space) {
ERR_FAIL_COND(is_initialized());

bool OpenXRAPI::set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space) {
requested_reference_space = p_requested_reference_space;

if (is_initialized()) {
return setup_play_space();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing we should add here is calling into the XRServer and resetting our reference frame transform. A user shouldn't be using center_on_hmd in combination with local and local floor reference spaces but just in case they did...

Similarly we should probably do that as well when the user trigger a recenter just before we emit the pose recenter signal, but only if the reference space isn't stage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing we should add here is calling into the XRServer and resetting our reference frame transform. A user shouldn't be using center_on_hmd in combination with local and local floor reference spaces but just in case they did...

Ah, good point! If they were in STAGE, used center_on_hmd(), then switched to LOCAL or LOCAL_FLOOR, they'd be stuck with the old reference frame.

In my latest push I've added an XRServer::clear_reference_frame() function which is called when changing the reference space. I've also exposed it, because I could imagine scenarios where a developer would want to clear any previous recentering, for example, when switching from VR to MR mode.

Similarly we should probably do that as well when the user trigger a recenter just before we emit the pose recenter signal, but only if the reference space isn't stage.

I'm less sure about this one. If we clear it when switching to LOCAL or LOCAL_FLOOR, and we give the developer the ability to clear it when they need to, I'm not sure this is necessary.

}

return true;
}

void OpenXRAPI::set_submit_depth_buffer(bool p_submit_depth_buffer) {
Expand Down Expand Up @@ -1466,7 +1484,12 @@ bool OpenXRAPI::initialize_session() {
return false;
}

if (!setup_spaces()) {
if (!setup_play_space()) {
destroy_session();
return false;
}

if (!setup_view_space()) {
destroy_session();
return false;
}
Expand Down
5 changes: 3 additions & 2 deletions modules/openxr/openxr_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,8 @@ class OpenXRAPI {
bool create_session();
bool load_supported_reference_spaces();
bool is_reference_space_supported(XrReferenceSpaceType p_reference_space);
bool setup_spaces();
bool setup_play_space();
bool setup_view_space();
bool load_supported_swapchain_formats();
bool is_swapchain_format_supported(int64_t p_swapchain_format);
bool create_swapchains();
Expand Down Expand Up @@ -338,7 +339,7 @@ class OpenXRAPI {
void set_view_configuration(XrViewConfigurationType p_view_configuration);
XrViewConfigurationType get_view_configuration() const { return view_configuration; }

void set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space);
bool set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space);
XrReferenceSpaceType get_requested_reference_space() const { return requested_reference_space; }
XrReferenceSpaceType get_reference_space() const { return reference_space; }

Expand Down
12 changes: 9 additions & 3 deletions modules/openxr/openxr_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,6 @@ XRInterface::PlayAreaMode OpenXRInterface::get_play_area_mode() const {
}

bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) {
ERR_FAIL_COND_V_MSG(initialized, false, "Cannot change play area mode after OpenXR interface has been initialized");
ERR_FAIL_NULL_V(openxr_api, false);

XrReferenceSpaceType reference_space;
Expand All @@ -726,8 +725,15 @@ bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) {
return false;
}

openxr_api->set_requested_reference_space(reference_space);
return true;
if (openxr_api->set_requested_reference_space(reference_space)) {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server) {
xr_server->clear_reference_frame();
}
return true;
}

return false;
}

PackedVector3Array OpenXRInterface::get_play_area() const {
Expand Down
5 changes: 5 additions & 0 deletions servers/xr_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ void XRServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_world_origin"), &XRServer::get_world_origin);
ClassDB::bind_method(D_METHOD("set_world_origin", "world_origin"), &XRServer::set_world_origin);
ClassDB::bind_method(D_METHOD("get_reference_frame"), &XRServer::get_reference_frame);
ClassDB::bind_method(D_METHOD("clear_reference_frame"), &XRServer::get_reference_frame);
ClassDB::bind_method(D_METHOD("center_on_hmd", "rotation_mode", "keep_height"), &XRServer::center_on_hmd);
ClassDB::bind_method(D_METHOD("get_hmd_transform"), &XRServer::get_hmd_transform);

Expand Down Expand Up @@ -158,6 +159,10 @@ void XRServer::center_on_hmd(RotationMode p_rotation_mode, bool p_keep_height) {
reference_frame = new_reference_frame.inverse();
};

void XRServer::clear_reference_frame() {
reference_frame = Transform3D();
}

Transform3D XRServer::get_hmd_transform() {
Transform3D hmd_transform;
if (primary_interface != nullptr) {
Expand Down
1 change: 1 addition & 0 deletions servers/xr_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ class XRServer : public Object {
and in the virtual world out of sync
*/
Transform3D get_reference_frame() const;
void clear_reference_frame();
void center_on_hmd(RotationMode p_rotation_mode, bool p_keep_height);

/*
Expand Down
Loading