From 5935bfa8603b7c22df4ea777a688723a408e6c5d Mon Sep 17 00:00:00 2001 From: David Snopek Date: Tue, 30 Jan 2024 10:30:54 -0600 Subject: [PATCH] OpenXR: Allow changing play area mode during active session --- doc/classes/XRInterface.xml | 1 + doc/classes/XRServer.xml | 6 ++ modules/openxr/openxr_api.cpp | 137 ++++++++++++++++------------ modules/openxr/openxr_api.h | 5 +- modules/openxr/openxr_interface.cpp | 12 ++- servers/xr_server.cpp | 5 + servers/xr_server.h | 1 + 7 files changed, 105 insertions(+), 62 deletions(-) diff --git a/doc/classes/XRInterface.xml b/doc/classes/XRInterface.xml index 29a5b75672a9..00ce6e2a8b83 100644 --- a/doc/classes/XRInterface.xml +++ b/doc/classes/XRInterface.xml @@ -141,6 +141,7 @@ 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. diff --git a/doc/classes/XRServer.xml b/doc/classes/XRServer.xml index e98b6f37809f..2f106af340a7 100644 --- a/doc/classes/XRServer.xml +++ b/doc/classes/XRServer.xml @@ -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. + + + + Clears the reference frame that was set by previous calls to [method center_on_hmd]. + + diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index eafabe03e787..126a27bb7562 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -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."); - - reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; - emulating_local_floor = true; - - // 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; - - } 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; - } - - XrReferenceSpaceCreateInfo play_space_create_info = { - XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type - nullptr, // next - reference_space, // referenceSpaceType - identityPose, // poseInReferenceSpace - }; - - 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; - } + 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; } - // 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; - } + XrReferenceSpaceCreateInfo play_space_create_info = { + XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type + nullptr, // next + new_reference_space, // 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 - }; + 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; + } - 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; - } + // 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; + + 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; + } + + 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; + } + + 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 + }; + + 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; @@ -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(); + } + + return true; } void OpenXRAPI::set_submit_depth_buffer(bool p_submit_depth_buffer) { @@ -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; } diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 6e55020aef82..4941acb04efd 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -232,7 +232,8 @@ private: 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(); @@ -338,7 +339,7 @@ public: 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; } diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index 6b311b73a8bf..ebcd331f3d75 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -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; @@ -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 { diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp index 0bc8dbee1858..e7f644d53fb4 100644 --- a/servers/xr_server.cpp +++ b/servers/xr_server.cpp @@ -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); @@ -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) { diff --git a/servers/xr_server.h b/servers/xr_server.h index 04cf6c1d1ee9..fe59fc22cb7d 100644 --- a/servers/xr_server.h +++ b/servers/xr_server.h @@ -142,6 +142,7 @@ public: 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); /*