Pārlūkot izejas kodu

Add support for OpenXR local floor extension

David Snopek 1 gadu atpakaļ
vecāks
revīzija
a8690cbeed

+ 1 - 1
doc/classes/XRInterface.xml

@@ -256,7 +256,7 @@
 			Player is free to move around, full positional tracking.
 		</constant>
 		<constant name="XR_PLAY_AREA_STAGE" value="4" enum="PlayAreaMode">
-			Same as [constant XR_PLAY_AREA_ROOMSCALE] but origin point is fixed to the center of the physical space, [method XRServer.center_on_hmd] disabled.
+			Same as [constant XR_PLAY_AREA_ROOMSCALE] but origin point is fixed to the center of the physical space. In this mode, system-level recentering may be disabled, requiring the use of [method XRServer.center_on_hmd].
 		</constant>
 		<constant name="XR_ENV_BLEND_MODE_OPAQUE" value="0" enum="EnvironmentBlendMode">
 			Opaque blend mode. This is typically used for VR devices.

+ 1 - 1
main/main.cpp

@@ -2229,7 +2229,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 	GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "xr/openxr/default_action_map", PROPERTY_HINT_FILE, "*.tres"), "res://openxr_action_map.tres");
 	GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/form_factor", PROPERTY_HINT_ENUM, "Head Mounted,Handheld"), "0");
 	GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/view_configuration", PROPERTY_HINT_ENUM, "Mono,Stereo"), "1"); // "Mono,Stereo,Quad,Observer"
-	GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/reference_space", PROPERTY_HINT_ENUM, "Local,Stage"), "1");
+	GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/reference_space", PROPERTY_HINT_ENUM, "Local,Stage,Local Floor"), "1");
 	GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/environment_blend_mode", PROPERTY_HINT_ENUM, "Opaque,Additive,Alpha"), "0");
 	GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/foveation_level", PROPERTY_HINT_ENUM, "Off,Low,Medium,High"), "0");
 	GLOBAL_DEF_BASIC("xr/openxr/foveation_dynamic", false);

+ 1 - 0
modules/openxr/SCsub

@@ -104,6 +104,7 @@ if env["vulkan"]:
 if env["opengl3"] and env["platform"] != "macos":
     env_openxr.add_source_files(module_obj, "extensions/openxr_opengl_extension.cpp")
 
+env_openxr.add_source_files(module_obj, "extensions/openxr_local_floor_extension.cpp")
 env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp")
 env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_depth_extension.cpp")
 env_openxr.add_source_files(module_obj, "extensions/openxr_eye_gaze_interaction.cpp")

+ 59 - 0
modules/openxr/extensions/openxr_local_floor_extension.cpp

@@ -0,0 +1,59 @@
+/**************************************************************************/
+/*  openxr_local_floor_extension.cpp                                      */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "openxr_local_floor_extension.h"
+
+#include "core/string/print_string.h"
+
+OpenXRLocalFloorExtension *OpenXRLocalFloorExtension::singleton = nullptr;
+
+OpenXRLocalFloorExtension *OpenXRLocalFloorExtension::get_singleton() {
+	return singleton;
+}
+
+OpenXRLocalFloorExtension::OpenXRLocalFloorExtension() {
+	singleton = this;
+}
+
+OpenXRLocalFloorExtension::~OpenXRLocalFloorExtension() {
+	singleton = nullptr;
+}
+
+HashMap<String, bool *> OpenXRLocalFloorExtension::get_requested_extensions() {
+	HashMap<String, bool *> request_extensions;
+
+	request_extensions[XR_EXT_LOCAL_FLOOR_EXTENSION_NAME] = &available;
+
+	return request_extensions;
+}
+
+bool OpenXRLocalFloorExtension::is_available() {
+	return available;
+}

+ 53 - 0
modules/openxr/extensions/openxr_local_floor_extension.h

@@ -0,0 +1,53 @@
+/**************************************************************************/
+/*  openxr_local_floor_extension.h                                        */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef OPENXR_LOCAL_FLOOR_EXTENSION_H
+#define OPENXR_LOCAL_FLOOR_EXTENSION_H
+
+#include "openxr_extension_wrapper.h"
+
+class OpenXRLocalFloorExtension : public OpenXRExtensionWrapper {
+public:
+	static OpenXRLocalFloorExtension *get_singleton();
+
+	OpenXRLocalFloorExtension();
+	virtual ~OpenXRLocalFloorExtension() override;
+
+	virtual HashMap<String, bool *> get_requested_extensions() override;
+
+	bool is_available();
+
+private:
+	static OpenXRLocalFloorExtension *singleton;
+
+	bool available = false;
+};
+
+#endif // OPENXR_LOCAL_FLOOR_EXTENSION_H

+ 108 - 15
modules/openxr/openxr_api.cpp

@@ -665,13 +665,6 @@ bool OpenXRAPI::load_supported_reference_spaces() {
 		print_verbose(String("OpenXR: Found supported reference space ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[i]));
 	}
 
-	// Check value we loaded at startup...
-	if (!is_reference_space_supported(reference_space)) {
-		print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(reference_space) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[0]));
-
-		reference_space = supported_reference_spaces[0];
-	}
-
 	return true;
 }
 
@@ -699,16 +692,31 @@ bool OpenXRAPI::setup_spaces() {
 
 	// create play space
 	{
-		if (!is_reference_space_supported(reference_space)) {
-			print_line("OpenXR: reference space ", OpenXRUtil::get_reference_space_name(reference_space), " is not supported.");
-			return false;
+		emulating_local_floor = false;
+
+		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
+			identityPose, // poseInReferenceSpace
 		};
 
 		result = xrCreateReferenceSpace(session, &play_space_create_info, &play_space);
@@ -742,6 +750,80 @@ bool OpenXRAPI::setup_spaces() {
 	return true;
 }
 
+bool OpenXRAPI::reset_emulated_floor_height() {
+	ERR_FAIL_COND_V(!emulating_local_floor, false);
+
+	// This is based on the example code in the OpenXR spec which shows how to
+	// emulate LOCAL_FLOOR if it's not supported.
+	// See: https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_local_floor
+
+	XrResult result;
+
+	XrPosef identityPose = {
+		{ 0.0, 0.0, 0.0, 1.0 },
+		{ 0.0, 0.0, 0.0 }
+	};
+
+	XrSpace local_space = XR_NULL_HANDLE;
+	XrSpace stage_space = XR_NULL_HANDLE;
+
+	XrReferenceSpaceCreateInfo create_info = {
+		XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
+		nullptr, // next
+		XR_REFERENCE_SPACE_TYPE_LOCAL, // referenceSpaceType
+		identityPose, // poseInReferenceSpace
+	};
+
+	result = xrCreateReferenceSpace(session, &create_info, &local_space);
+	if (XR_FAILED(result)) {
+		print_line("OpenXR: Failed to create LOCAL space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
+		return false;
+	}
+
+	create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
+	result = xrCreateReferenceSpace(session, &create_info, &stage_space);
+	if (XR_FAILED(result)) {
+		print_line("OpenXR: Failed to create STAGE space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
+		xrDestroySpace(local_space);
+		return false;
+	}
+
+	XrSpaceLocation stage_location = {
+		XR_TYPE_SPACE_LOCATION, // type
+		nullptr, // next
+		0, // locationFlags
+		identityPose, // pose
+	};
+
+	result = xrLocateSpace(stage_space, local_space, get_next_frame_time(), &stage_location);
+
+	xrDestroySpace(local_space);
+	xrDestroySpace(stage_space);
+
+	if (XR_FAILED(result)) {
+		print_line("OpenXR: Failed to locate STAGE space in LOCAL space, in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
+		return false;
+	}
+
+	XrSpace new_play_space;
+	create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
+	create_info.poseInReferenceSpace.position.y = stage_location.pose.position.y;
+	result = xrCreateReferenceSpace(session, &create_info, &new_play_space);
+	if (XR_FAILED(result)) {
+		print_line("OpenXR: Failed to recreate emulated LOCAL_FLOOR play space with latest floor estimate [", get_error_string(result), "]");
+		return false;
+	}
+
+	xrDestroySpace(play_space);
+	play_space = new_play_space;
+
+	// If we've made it this far, it means we can properly emulate LOCAL_FLOOR, so we'll
+	// report that as the reference space to the outside world.
+	reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
+
+	return true;
+}
+
 bool OpenXRAPI::load_supported_swapchain_formats() {
 	ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
 
@@ -1180,10 +1262,10 @@ void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configurat
 	view_configuration = p_view_configuration;
 }
 
-void OpenXRAPI::set_reference_space(XrReferenceSpaceType p_reference_space) {
+void OpenXRAPI::set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space) {
 	ERR_FAIL_COND(is_initialized());
 
-	reference_space = p_reference_space;
+	requested_reference_space = p_requested_reference_space;
 }
 
 void OpenXRAPI::set_submit_depth_buffer(bool p_submit_depth_buffer) {
@@ -1628,6 +1710,9 @@ bool OpenXRAPI::poll_events() {
 				XrEventDataReferenceSpaceChangePending *event = (XrEventDataReferenceSpaceChangePending *)&runtimeEvent;
 
 				print_verbose(String("OpenXR EVENT: reference space type ") + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!");
+				if (emulating_local_floor) {
+					should_reset_emulated_floor_height = true;
+				}
 				if (event->poseValid && xr_interface) {
 					xr_interface->on_pose_recentered();
 				}
@@ -1783,6 +1868,11 @@ void OpenXRAPI::pre_render() {
 		frame_state.predictedDisplayPeriod = 0;
 	}
 
+	if (unlikely(should_reset_emulated_floor_height)) {
+		reset_emulated_floor_height();
+		should_reset_emulated_floor_height = false;
+	}
+
 	for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
 		wrapper->on_pre_render();
 	}
@@ -2136,10 +2226,13 @@ OpenXRAPI::OpenXRAPI() {
 		int reference_space_setting = GLOBAL_GET("xr/openxr/reference_space");
 		switch (reference_space_setting) {
 			case 0: {
-				reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
+				requested_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
 			} break;
 			case 1: {
-				reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+				requested_reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+			} break;
+			case 2: {
+				requested_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
 			} break;
 			default:
 				break;

+ 8 - 2
modules/openxr/openxr_api.h

@@ -98,7 +98,8 @@ private:
 	// configuration
 	XrFormFactor form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
 	XrViewConfigurationType view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
-	XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+	XrReferenceSpaceType requested_reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+	XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
 	bool submit_depth_buffer = false; // if set to true we submit depth buffers to OpenXR if a suitable extension is enabled.
 
 	// blend mode
@@ -149,6 +150,10 @@ private:
 	bool view_pose_valid = false;
 	XRPose::TrackingConfidence head_pose_confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE;
 
+	bool emulating_local_floor = false;
+	bool should_reset_emulated_floor_height = false;
+	bool reset_emulated_floor_height();
+
 	bool load_layer_properties();
 	bool load_supported_extensions();
 	bool is_extension_supported(const String &p_extension) const;
@@ -333,7 +338,8 @@ public:
 	void set_view_configuration(XrViewConfigurationType p_view_configuration);
 	XrViewConfigurationType get_view_configuration() const { return view_configuration; }
 
-	void set_reference_space(XrReferenceSpaceType p_reference_space);
+	void 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; }
 
 	void set_submit_depth_buffer(bool p_submit_depth_buffer);

+ 35 - 2
modules/openxr/openxr_interface.cpp

@@ -686,15 +686,48 @@ Dictionary OpenXRInterface::get_system_info() {
 }
 
 bool OpenXRInterface::supports_play_area_mode(XRInterface::PlayAreaMode p_mode) {
-	return false;
+	if (p_mode == XRInterface::XR_PLAY_AREA_3DOF) {
+		return false;
+	}
+	return true;
 }
 
 XRInterface::PlayAreaMode OpenXRInterface::get_play_area_mode() const {
+	if (!openxr_api || !initialized) {
+		return XRInterface::XR_PLAY_AREA_UNKNOWN;
+	}
+
+	XrReferenceSpaceType reference_space = openxr_api->get_reference_space();
+
+	if (reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL) {
+		return XRInterface::XR_PLAY_AREA_SITTING;
+	} else if (reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT) {
+		return XRInterface::XR_PLAY_AREA_ROOMSCALE;
+	} else if (reference_space == XR_REFERENCE_SPACE_TYPE_STAGE) {
+		return XRInterface::XR_PLAY_AREA_STAGE;
+	}
+
 	return XRInterface::XR_PLAY_AREA_UNKNOWN;
 }
 
 bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) {
-	return false;
+	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;
+
+	if (p_mode == XRInterface::XR_PLAY_AREA_SITTING) {
+		reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
+	} else if (p_mode == XRInterface::XR_PLAY_AREA_ROOMSCALE) {
+		reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
+	} else if (p_mode == XRInterface::XR_PLAY_AREA_STAGE) {
+		reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
+	} else {
+		return false;
+	}
+
+	openxr_api->set_requested_reference_space(reference_space);
+	return true;
 }
 
 PackedVector3Array OpenXRInterface::get_play_area() const {

+ 2 - 0
modules/openxr/register_types.cpp

@@ -49,6 +49,7 @@
 #include "extensions/openxr_htc_controller_extension.h"
 #include "extensions/openxr_htc_vive_tracker_extension.h"
 #include "extensions/openxr_huawei_controller_extension.h"
+#include "extensions/openxr_local_floor_extension.h"
 #include "extensions/openxr_meta_controller_extension.h"
 #include "extensions/openxr_ml2_controller_extension.h"
 #include "extensions/openxr_palm_pose_extension.h"
@@ -107,6 +108,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
 
 			// register our other extensions
 			OpenXRAPI::register_extension_wrapper(memnew(OpenXRPalmPoseExtension));
+			OpenXRAPI::register_extension_wrapper(memnew(OpenXRLocalFloorExtension));
 			OpenXRAPI::register_extension_wrapper(memnew(OpenXRPicoControllerExtension));
 			OpenXRAPI::register_extension_wrapper(memnew(OpenXRCompositionLayerDepthExtension));
 			OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCControllerExtension));

+ 1 - 1
servers/xr/xr_interface.h

@@ -75,7 +75,7 @@ public:
 		XR_PLAY_AREA_3DOF, /* Only support orientation tracking, no positional tracking, area will center around player */
 		XR_PLAY_AREA_SITTING, /* Player is in seated position, limited positional tracking, fixed guardian around player */
 		XR_PLAY_AREA_ROOMSCALE, /* Player is free to move around, full positional tracking */
-		XR_PLAY_AREA_STAGE, /* Same as roomscale but origin point is fixed to the center of the physical space, XRServer.center_on_hmd disabled */
+		XR_PLAY_AREA_STAGE, /* Same as roomscale but origin point is fixed to the center of the physical space */
 	};
 
 	enum EnvironmentBlendMode {

+ 0 - 6
servers/xr_server.cpp

@@ -129,12 +129,6 @@ void XRServer::center_on_hmd(RotationMode p_rotation_mode, bool p_keep_height) {
 		return;
 	}
 
-	if (primary_interface->get_play_area_mode() == XRInterface::XR_PLAY_AREA_STAGE) {
-		// center_on_hmd is not available in this mode
-		reference_frame = Transform3D();
-		return;
-	}
-
 	// clear our current reference frame or we'll end up double adjusting it
 	reference_frame = Transform3D();