From 2a6199aa0f7d97f101e7b4bdf78f04b53c896d43 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Thu, 7 Apr 2022 13:22:19 -0500 Subject: [PATCH] Add WebXRInterface.xr_standard_mapping flag to attempt to convert button/axis ids to match other AR/VR interfaces --- modules/webxr/doc_classes/WebXRInterface.xml | 7 ++ modules/webxr/godot_webxr.h | 4 +- modules/webxr/native/library_godot_webxr.js | 95 ++++++++++++++++---- modules/webxr/webxr_interface.cpp | 3 + modules/webxr/webxr_interface.h | 2 + modules/webxr/webxr_interface_js.cpp | 18 ++-- modules/webxr/webxr_interface_js.h | 3 + 7 files changed, 107 insertions(+), 25 deletions(-) diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml index 73c069021140..ade519b9a8d0 100644 --- a/modules/webxr/doc_classes/WebXRInterface.xml +++ b/modules/webxr/doc_classes/WebXRInterface.xml @@ -22,6 +22,9 @@ webxr_interface = ARVRServer.find_interface("WebXR") if webxr_interface: + # Map to the standard button/axis ids when possible. + webxr_interface.xr_standard_mapping = true + # WebXR uses a lot of asynchronous callbacks, so we connect to various # signals in order to receive them. webxr_interface.connect("session_supported", self, "_webxr_session_supported") @@ -164,6 +167,10 @@ Indicates if the WebXR session's imagery is visible to the user. Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRVisibilityState]WebXR's XRVisibilityState[/url], including [code]"hidden"[/code], [code]"visible"[/code], and [code]"visible-blurred"[/code]. + + If set to true, the button and axes ids will be converted to match the standard ids used by other AR/VR interfaces, when possible. + Otherwise, the ids will be passed through unaltered from WebXR. + diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h index 2f4519bce3e1..df3f4b29d8c5 100644 --- a/modules/webxr/godot_webxr.h +++ b/modules/webxr/godot_webxr.h @@ -80,8 +80,8 @@ extern void godot_webxr_sample_controller_data(); extern int godot_webxr_get_controller_count(); extern int godot_webxr_is_controller_connected(int p_controller); extern float *godot_webxr_get_controller_transform(int p_controller); -extern int *godot_webxr_get_controller_buttons(int p_controller); -extern int *godot_webxr_get_controller_axes(int p_controller); +extern int *godot_webxr_get_controller_buttons(int p_controller, bool p_xr_standard_mapping); +extern int *godot_webxr_get_controller_axes(int p_controller, bool p_xr_standard_mapping); extern int godot_webxr_get_controller_target_ray_mode(int p_controller); extern char *godot_webxr_get_visibility_state(); diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js index 2dff39c42c32..da29ea15286b 100644 --- a/modules/webxr/native/library_godot_webxr.js +++ b/modules/webxr/native/library_godot_webxr.js @@ -524,8 +524,8 @@ const GodotWebXR = { }, godot_webxr_get_controller_buttons__proxy: 'sync', - godot_webxr_get_controller_buttons__sig: 'ii', - godot_webxr_get_controller_buttons: function (p_controller) { + godot_webxr_get_controller_buttons__sig: 'iii', + godot_webxr_get_controller_buttons: function (p_controller, p_xr_standard_mapping) { if (GodotWebXR.controllers.length === 0) { return 0; } @@ -535,19 +535,55 @@ const GodotWebXR = { return 0; } - const button_count = controller.gamepad.buttons.length; + let buttons = controller.gamepad.buttons; + if (controller.gamepad.mapping === 'xr-standard' && p_xr_standard_mapping) { + buttons = [ + // 0 = unused, + 0, + // 1 = B/Y + buttons[5], + // 2 = Grip + buttons[1], + // 3 + buttons[3], + // 4 + buttons[6], + // 5 + buttons[7], + // 6 + buttons[8], + // 7 = A/X + buttons[4], + // 8 + buttons[9], + // 9 + buttons[10], + // 10 + buttons[11], + // 11 + buttons[12], + // 12 + buttons[13], + // 13 + buttons[14], + // 14 = Pad + buttons[2], + // 15 = Trigger + buttons[0], + ]; + } - const buf = GodotRuntime.malloc((button_count + 1) * 4); - GodotRuntime.setHeapValue(buf, button_count, 'i32'); - for (let i = 0; i < button_count; i++) { - GodotRuntime.setHeapValue(buf + 4 + (i * 4), controller.gamepad.buttons[i].value, 'float'); + const buf = GodotRuntime.malloc((buttons.length + 1) * 4); + GodotRuntime.setHeapValue(buf, buttons.length, 'i32'); + for (let i = 0; i < buttons.length; i++) { + GodotRuntime.setHeapValue(buf + 4 + (i * 4), (buttons[i] ? buttons[i].value : 0.0), 'float'); } return buf; }, godot_webxr_get_controller_axes__proxy: 'sync', - godot_webxr_get_controller_axes__sig: 'ii', - godot_webxr_get_controller_axes: function (p_controller) { + godot_webxr_get_controller_axes__sig: 'iii', + godot_webxr_get_controller_axes: function (p_controller, p_xr_standard_mapping) { if (GodotWebXR.controllers.length === 0) { return 0; } @@ -557,18 +593,41 @@ const GodotWebXR = { return 0; } - const axes_count = controller.gamepad.axes.length; - - const buf = GodotRuntime.malloc((axes_count + 1) * 4); - GodotRuntime.setHeapValue(buf, axes_count, 'i32'); - for (let i = 0; i < axes_count; i++) { - let value = controller.gamepad.axes[i]; - if (i === 1 || i === 3) { + let axes = controller.gamepad.axes; + if (controller.gamepad.mapping === 'xr-standard') { + if (p_xr_standard_mapping) { + const trigger_axis = controller.gamepad.buttons[0].value; + const grip_axis = controller.gamepad.buttons[1].value; + axes = [ + // 0 = Thumbstick X + axes[2], + // 1 = Thumbstick Y + axes[3] * -1.0, + // 2 = Trigger + trigger_axis, + // 3 = Grip (to match mistake in Oculus mobile plugin). + grip_axis, + // 4 = Grip + grip_axis, + // 5 = unused + 0, + // 6 = Trackpad X + axes[0], + // 7 = Trackpad Y + axes[1] * -1.0, + ]; + } else { // Invert the Y-axis on thumbsticks and trackpads, in order to // match OpenXR and other XR platform SDKs. - value *= -1.0; + axes[1] *= -1.0; + axes[3] *= -1.0; } - GodotRuntime.setHeapValue(buf + 4 + (i * 4), value, 'float'); + } + + const buf = GodotRuntime.malloc((axes.length + 1) * 4); + GodotRuntime.setHeapValue(buf, axes.length, 'i32'); + for (let i = 0; i < axes.length; i++) { + GodotRuntime.setHeapValue(buf + 4 + (i * 4), axes[i], 'float'); } return buf; }, diff --git a/modules/webxr/webxr_interface.cpp b/modules/webxr/webxr_interface.cpp index 998db06083a4..73a9f3abde73 100644 --- a/modules/webxr/webxr_interface.cpp +++ b/modules/webxr/webxr_interface.cpp @@ -46,6 +46,8 @@ void WebXRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("get_controller_target_ray_mode", "controller_id"), &WebXRInterface::get_controller_target_ray_mode); ClassDB::bind_method(D_METHOD("get_visibility_state"), &WebXRInterface::get_visibility_state); ClassDB::bind_method(D_METHOD("get_bounds_geometry"), &WebXRInterface::get_bounds_geometry); + ClassDB::bind_method(D_METHOD("set_xr_standard_mapping"), &WebXRInterface::set_xr_standard_mapping); + ClassDB::bind_method(D_METHOD("get_xr_standard_mapping"), &WebXRInterface::get_xr_standard_mapping); ADD_PROPERTY(PropertyInfo(Variant::STRING, "session_mode", PROPERTY_HINT_NONE), "set_session_mode", "get_session_mode"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "required_features", PROPERTY_HINT_NONE), "set_required_features", "get_required_features"); @@ -54,6 +56,7 @@ void WebXRInterface::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "reference_space_type", PROPERTY_HINT_NONE), "", "get_reference_space_type"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "visibility_state", PROPERTY_HINT_NONE), "", "get_visibility_state"); ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR3_ARRAY, "bounds_geometry", PROPERTY_HINT_NONE), "", "get_bounds_geometry"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "xr_standard_mapping", PROPERTY_HINT_NONE), "set_xr_standard_mapping", "get_xr_standard_mapping"); ADD_SIGNAL(MethodInfo("session_supported", PropertyInfo(Variant::STRING, "session_mode"), PropertyInfo(Variant::BOOL, "supported"))); ADD_SIGNAL(MethodInfo("session_started")); diff --git a/modules/webxr/webxr_interface.h b/modules/webxr/webxr_interface.h index fc68d861a25b..89e0b2f65abf 100644 --- a/modules/webxr/webxr_interface.h +++ b/modules/webxr/webxr_interface.h @@ -68,6 +68,8 @@ class WebXRInterface : public ARVRInterface { virtual TargetRayMode get_controller_target_ray_mode(int p_controller_id) const = 0; virtual String get_visibility_state() const = 0; virtual PoolVector3Array get_bounds_geometry() const = 0; + virtual void set_xr_standard_mapping(bool p_xr_standard_mapping) = 0; + virtual bool get_xr_standard_mapping() const = 0; }; VARIANT_ENUM_CAST(WebXRInterface::TargetRayMode); diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index c4d2738dc744..3d1a78bd17ac 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -204,6 +204,14 @@ PoolVector3Array WebXRInterfaceJS::get_bounds_geometry() const { return ret; } +void WebXRInterfaceJS::set_xr_standard_mapping(bool p_xr_standard_mapping) { + xr_standard_mapping = p_xr_standard_mapping; +} + +bool WebXRInterfaceJS::get_xr_standard_mapping() const { + return xr_standard_mapping; +} + StringName WebXRInterfaceJS::get_name() const { return "WebXR"; }; @@ -417,7 +425,7 @@ void WebXRInterfaceJS::_update_tracker(int p_controller_id) { free(tracker_matrix); } - int *buttons = godot_webxr_get_controller_buttons(p_controller_id); + int *buttons = godot_webxr_get_controller_buttons(p_controller_id, xr_standard_mapping); if (buttons) { for (int i = 0; i < buttons[0]; i++) { input->joy_button(joy_id, i, *((float *)buttons + (i + 1))); @@ -425,7 +433,7 @@ void WebXRInterfaceJS::_update_tracker(int p_controller_id) { free(buttons); } - int *axes = godot_webxr_get_controller_axes(p_controller_id); + int *axes = godot_webxr_get_controller_axes(p_controller_id, xr_standard_mapping); if (axes) { WebXRInterface::TargetRayMode target_ray_mode = (WebXRInterface::TargetRayMode)godot_webxr_get_controller_target_ray_mode(p_controller_id); if (target_ray_mode == WebXRInterface::TARGET_RAY_MODE_SCREEN) { @@ -481,7 +489,7 @@ void WebXRInterfaceJS::_on_input_event(int p_event_type, int p_input_source) { touching[touch_index] = (p_event_type == WEBXR_INPUT_EVENT_SELECTSTART); } - int *axes = godot_webxr_get_controller_axes(p_input_source); + int *axes = godot_webxr_get_controller_axes(p_input_source, false); if (axes) { Vector2 joy_vector = _get_joy_vector_from_axes(axes); Vector2 position = _get_screen_position_from_joy_vector(joy_vector); @@ -554,8 +562,7 @@ Vector2 WebXRInterfaceJS::_get_joy_vector_from_axes(int *p_axes) { } Vector2 WebXRInterfaceJS::_get_screen_position_from_joy_vector(const Vector2 &p_joy_vector) { - // Invert the y-axis. - Vector2 position_percentage((p_joy_vector.x + 1.0f) / 2.0f, ((-p_joy_vector.y) + 1.0f) / 2.0f); + Vector2 position_percentage((p_joy_vector.x + 1.0f) / 2.0f, ((p_joy_vector.y) + 1.0f) / 2.0f); Vector2 position = get_render_targetsize() * position_percentage; return position; @@ -567,6 +574,7 @@ void WebXRInterfaceJS::notification(int p_what) { WebXRInterfaceJS::WebXRInterfaceJS() { initialized = false; + xr_standard_mapping = false; session_mode = "inline"; requested_reference_space_types = "local"; }; diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index 768c86ecc342..167ebef089c0 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -46,6 +46,7 @@ class WebXRInterfaceJS : public WebXRInterface { private: bool initialized; + bool xr_standard_mapping; String session_mode; String required_features; @@ -80,6 +81,8 @@ class WebXRInterfaceJS : public WebXRInterface { virtual TargetRayMode get_controller_target_ray_mode(int p_controller_id) const; virtual String get_visibility_state() const; virtual PoolVector3Array get_bounds_geometry() const; + virtual void set_xr_standard_mapping(bool p_xr_standard_mapping); + virtual bool get_xr_standard_mapping() const; virtual StringName get_name() const; virtual int get_capabilities() const;