From 051dc97297c95893fb9f2b463f6d610de708e631 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Fri, 7 Oct 2022 10:44:05 -0500 Subject: [PATCH] Convert from Godot 3 PR https://github.com/godotengine/godot/pull/56819 --- modules/webxr/doc_classes/WebXRInterface.xml | 22 +++ modules/webxr/godot_webxr.h | 12 +- modules/webxr/native/library_godot_webxr.js | 40 +++++- modules/webxr/webxr_interface.cpp | 6 + modules/webxr/webxr_interface.h | 10 ++ modules/webxr/webxr_interface_js.cpp | 134 ++++++++++++++++++- modules/webxr/webxr_interface_js.h | 7 + 7 files changed, 221 insertions(+), 10 deletions(-) diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml index 49ff454f076992..6199de8a35a933 100644 --- a/modules/webxr/doc_classes/WebXRInterface.xml +++ b/modules/webxr/doc_classes/WebXRInterface.xml @@ -109,6 +109,14 @@ - [signal squeezestart] + + + + + Returns the target ray mode for the given [code]controller_id[/code]. + This can help interpret the input coming from that controller. See [url=https://developer.mozilla.org/en-US/docs/Web/API/XRInputSource/targetRayMode]XRInputSource.targetRayMode[/url] for more information. + + @@ -239,4 +247,18 @@ + + + We don't know the the target ray mode. + + + Target ray originates at the viewer's eyes and points in the direction they are looking. + + + Target ray from a handheld pointer, most likely a VR touch controller. + + + Target ray from touch screen, mouse or other tactile input device. + + diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h index d8d5bd99cc0e10..26328dc56d1504 100644 --- a/modules/webxr/godot_webxr.h +++ b/modules/webxr/godot_webxr.h @@ -37,12 +37,21 @@ extern "C" { #include "stddef.h" +enum WebXRInputEvent { + WEBXR_INPUT_EVENT_SELECTSTART, + WEBXR_INPUT_EVENT_SELECTEND, + WEBXR_INPUT_EVENT_SELECT, + WEBXR_INPUT_EVENT_SQUEEZESTART, + WEBXR_INPUT_EVENT_SQUEEZEEND, + WEBXR_INPUT_EVENT_SQUEEZE, +}; + typedef void (*GodotWebXRSupportedCallback)(char *p_session_mode, int p_supported); typedef void (*GodotWebXRStartedCallback)(char *p_reference_space_type); typedef void (*GodotWebXREndedCallback)(); typedef void (*GodotWebXRFailedCallback)(char *p_message); typedef void (*GodotWebXRControllerCallback)(); -typedef void (*GodotWebXRInputEventCallback)(char *p_signal_name, int p_controller_id); +typedef void (*GodotWebXRInputEventCallback)(int p_event_type, int p_controller_id); typedef void (*GodotWebXRSimpleEventCallback)(char *p_signal_name); extern int godot_webxr_is_supported(); @@ -73,6 +82,7 @@ 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_target_ray_mode(int p_controller); extern char *godot_webxr_get_visibility_state(); extern int *godot_webxr_get_bounds_geometry(); diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js index c476a54c5978f7..7d4875cf7da072 100644 --- a/modules/webxr/native/library_godot_webxr.js +++ b/modules/webxr/native/library_godot_webxr.js @@ -81,7 +81,7 @@ const GodotWebXR = { // the first element, and the right hand is the second element, and any // others placed at the 3rd position and up. sampleControllers: () => { - if (!GodotWebXR.session || !GodotWebXR.frame) { + if (!GodotWebXR.session) { return; } @@ -174,11 +174,12 @@ const GodotWebXR = { } }); - ['selectstart', 'select', 'selectend', 'squeezestart', 'squeeze', 'squeezeend'].forEach((input_event) => { + ['selectstart', 'selectend', 'select', 'squeezestart', 'squeezeend', 'squeeze'].forEach((input_event, index) => { session.addEventListener(input_event, function (evt) { - const c_str = GodotRuntime.allocString(input_event); - oninputevent(c_str, GodotWebXR.getControllerId(evt.inputSource)); - GodotRuntime.free(c_str); + // Some controllers won't exist until an event occurs, + // for example, with "screen" input sources (touch). + GodotWebXR.sampleControllers(); + oninputevent(index, GodotWebXR.getControllerId(evt.inputSource)); }); }); @@ -489,6 +490,35 @@ const GodotWebXR = { return buf; }, + godot_webxr_get_controller_target_ray_mode__proxy: 'sync', + godot_webxr_get_controller_target_ray_mode__sig: 'ii', + godot_webxr_get_controller_target_ray_mode: function (p_controller) { + if (p_controller < 0 || p_controller >= GodotWebXR.controllers.length) { + return 0; + } + + const controller = GodotWebXR.controllers[p_controller]; + if (!controller) { + return 0; + } + + switch (controller.targetRayMode) { + case 'gaze': + return 1; + + case 'tracked-pointer': + return 2; + + case 'screen': + return 3; + + default: + break; + } + + return 0; + }, + godot_webxr_get_visibility_state__proxy: 'sync', godot_webxr_get_visibility_state__sig: 'i', godot_webxr_get_visibility_state: function () { diff --git a/modules/webxr/webxr_interface.cpp b/modules/webxr/webxr_interface.cpp index b0ad53523a46d2..5b4eb79639dea8 100644 --- a/modules/webxr/webxr_interface.cpp +++ b/modules/webxr/webxr_interface.cpp @@ -43,6 +43,7 @@ void WebXRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("set_requested_reference_space_types", "requested_reference_space_types"), &WebXRInterface::set_requested_reference_space_types); ClassDB::bind_method(D_METHOD("get_requested_reference_space_types"), &WebXRInterface::get_requested_reference_space_types); ClassDB::bind_method(D_METHOD("get_controller", "controller_id"), &WebXRInterface::get_controller); + 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); @@ -68,4 +69,9 @@ void WebXRInterface::_bind_methods() { ADD_SIGNAL(MethodInfo("visibility_state_changed")); ADD_SIGNAL(MethodInfo("reference_space_reset")); + + BIND_ENUM_CONSTANT(TARGET_RAY_MODE_UNKNOWN); + BIND_ENUM_CONSTANT(TARGET_RAY_MODE_GAZE); + BIND_ENUM_CONSTANT(TARGET_RAY_MODE_TRACKED_POINTER); + BIND_ENUM_CONSTANT(TARGET_RAY_MODE_SCREEN); } diff --git a/modules/webxr/webxr_interface.h b/modules/webxr/webxr_interface.h index 801643bfa66c4c..d3c4593b270ee9 100644 --- a/modules/webxr/webxr_interface.h +++ b/modules/webxr/webxr_interface.h @@ -45,6 +45,13 @@ class WebXRInterface : public XRInterface { static void _bind_methods(); public: + enum TargetRayMode { + TARGET_RAY_MODE_UNKNOWN, + TARGET_RAY_MODE_GAZE, + TARGET_RAY_MODE_TRACKED_POINTER, + TARGET_RAY_MODE_SCREEN, + }; + virtual void is_session_supported(const String &p_session_mode) = 0; virtual void set_session_mode(String p_session_mode) = 0; virtual String get_session_mode() const = 0; @@ -56,8 +63,11 @@ class WebXRInterface : public XRInterface { virtual String get_requested_reference_space_types() const = 0; virtual String get_reference_space_type() const = 0; virtual Ref get_controller(int p_controller_id) const = 0; + virtual TargetRayMode get_controller_target_ray_mode(int p_controller_id) const = 0; virtual String get_visibility_state() const = 0; virtual PackedVector3Array get_bounds_geometry() const = 0; }; +VARIANT_ENUM_CAST(WebXRInterface::TargetRayMode); + #endif // WEBXR_INTERFACE_H diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index f6ed9f027ee10e..bf56ed49da6b8a 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -37,6 +37,8 @@ #include "drivers/gles3/storage/texture_storage.h" #include "emscripten.h" #include "godot_webxr.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" #include "servers/rendering/renderer_compositor.h" #include "servers/rendering/rendering_server_globals.h" @@ -99,15 +101,14 @@ void _emwebxr_on_controller_changed() { static_cast(interface.ptr())->_on_controller_changed(); } -extern "C" EMSCRIPTEN_KEEPALIVE void _emwebxr_on_input_event(char *p_signal_name, int p_input_source) { +extern "C" EMSCRIPTEN_KEEPALIVE void _emwebxr_on_input_event(int p_event_type, int p_input_source) { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL(xr_server); Ref interface = xr_server->find_interface("WebXR"); ERR_FAIL_COND(interface.is_null()); - StringName signal_name = StringName(p_signal_name); - interface->emit_signal(signal_name, p_input_source + 1); + ((WebXRInterfaceJS *)interface.ptr())->_on_input_event(p_event_type, p_input_source); } extern "C" EMSCRIPTEN_KEEPALIVE void _emwebxr_on_simple_event(char *p_signal_name) { @@ -177,6 +178,15 @@ Ref WebXRInterfaceJS::get_controller(int p_controller_id) c return Ref(); } +WebXRInterface::TargetRayMode WebXRInterfaceJS::get_controller_target_ray_mode(int p_controller_id) const { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL_V(xr_server, WebXRInterface::TARGET_RAY_MODE_UNKNOWN); + + ERR_FAIL_COND_V(p_controller_id <= 0, WebXRInterface::TARGET_RAY_MODE_UNKNOWN); + + return (WebXRInterface::TargetRayMode)godot_webxr_get_controller_target_ray_mode(p_controller_id - 1); +} + String WebXRInterfaceJS::get_visibility_state() const { char *c_str = godot_webxr_get_visibility_state(); if (c_str) { @@ -245,6 +255,10 @@ bool WebXRInterfaceJS::initialize() { // make this our primary interface xr_server->set_primary_interface(this); + // Clear state variables. + memset(controllers_state, 0, sizeof controllers_state); + memset(touching, 0, sizeof touching); + // Clear render_targetsize to make sure it gets reset to the new size. // Clearing in uninitialize() doesn't work because a frame can still be // rendered after it's called, which will fill render_targetsize again. @@ -494,6 +508,27 @@ void WebXRInterfaceJS::_update_tracker(int p_controller_id) { int *axes = godot_webxr_get_controller_axes(p_controller_id); 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) { + int touch_index = _get_touch_index(p_controller_id); + if (touch_index < 5 && touching[touch_index]) { + Vector2 joy_vector = _get_joy_vector_from_axes(axes); + Vector2 previous_joy_vector(tracker->get_input("axis_0"), tracker->get_input("axis_1")); + + if (!joy_vector.is_equal_approx(previous_joy_vector)) { + Vector2 position = _get_screen_position_from_joy_vector(joy_vector); + Vector2 previous_position = _get_screen_position_from_joy_vector(previous_joy_vector); + + Ref event; + event.instantiate(); + event->set_index(touch_index); + event->set_position(position); + event->set_relative(position - previous_position); + Input::get_singleton()->parse_input_event(event); + } + } + } + // TODO again just a temporary fix, split these between proper float and vector2 inputs for (int i = 0; i < axes[0]; i++) { char name[1024]; @@ -516,12 +551,103 @@ void WebXRInterfaceJS::_on_controller_changed() { for (int i = 0; i < 2; i++) { bool controller_connected = godot_webxr_is_controller_connected(i); if (controllers_state[i] != controller_connected) { - // Input::get_singleton()->joy_connection_changed(i + 100, controller_connected, i == 0 ? "Left" : "Right", ""); controllers_state[i] = controller_connected; } } } +void WebXRInterfaceJS::_on_input_event(int p_event_type, int p_input_source) { + if (p_event_type == WEBXR_INPUT_EVENT_SELECTSTART || p_event_type == WEBXR_INPUT_EVENT_SELECTEND) { + int target_ray_mode = godot_webxr_get_controller_target_ray_mode(p_input_source); + if (target_ray_mode == WebXRInterface::TARGET_RAY_MODE_SCREEN) { + int touch_index = _get_touch_index(p_input_source); + if (touch_index < 5) { + touching[touch_index] = (p_event_type == WEBXR_INPUT_EVENT_SELECTSTART); + } + + int *axes = godot_webxr_get_controller_axes(p_input_source); + if (axes) { + Vector2 joy_vector = _get_joy_vector_from_axes(axes); + Vector2 position = _get_screen_position_from_joy_vector(joy_vector); + free(axes); + + Input *input = Input::get_singleton(); + + Ref event; + event.instantiate(); + event->set_index(touch_index); + event->set_position(position); + event->set_pressed(p_event_type == WEBXR_INPUT_EVENT_SELECTSTART); + input->parse_input_event(event); + } + } + } + + int controller_id = p_input_source + 1; + switch (p_event_type) { + case WEBXR_INPUT_EVENT_SELECTSTART: + emit_signal("selectstart", controller_id); + break; + + case WEBXR_INPUT_EVENT_SELECTEND: + emit_signal("selectend", controller_id); + break; + + case WEBXR_INPUT_EVENT_SELECT: + emit_signal("select", controller_id); + break; + + case WEBXR_INPUT_EVENT_SQUEEZESTART: + emit_signal("squeezestart", controller_id); + break; + + case WEBXR_INPUT_EVENT_SQUEEZEEND: + emit_signal("squeezeend", controller_id); + break; + + case WEBXR_INPUT_EVENT_SQUEEZE: + emit_signal("squeeze", controller_id); + break; + } +} + +int WebXRInterfaceJS::_get_touch_index(int p_input_source) { + int index = 0; + for (int i = 0; i < p_input_source; i++) { + if (godot_webxr_get_controller_target_ray_mode(i) == WebXRInterface::TARGET_RAY_MODE_SCREEN) { + index++; + } + } + return index; +} + +Vector2 WebXRInterfaceJS::_get_joy_vector_from_axes(int *p_axes) { + float x_axis = 0.0; + float y_axis = 0.0; + + if (p_axes[0] >= 2) { + x_axis = *((float *)p_axes + 1); + y_axis = *((float *)p_axes + 2); + } + + return Vector2(x_axis, y_axis); +} + +Vector2 WebXRInterfaceJS::_get_screen_position_from_joy_vector(const Vector2 &p_joy_vector) { + SceneTree *scene_tree = Object::cast_to(OS::get_singleton()->get_main_loop()); + if (!scene_tree) { + return Vector2(); + } + + Window *viewport = scene_tree->get_root(); + + // 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 = viewport->get_size() * position_percentage; + + return position; +} + WebXRInterfaceJS::WebXRInterfaceJS() { initialized = false; session_mode = "inline"; diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index 319adc2ac991d8..bb8c0b8d77b529 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -56,11 +56,16 @@ class WebXRInterfaceJS : public WebXRInterface { // TODO maybe turn into a vector to support more then 2 controllers... bool controllers_state[2]; Ref controllers[2]; + bool touching[5]; Size2 render_targetsize; Transform3D _js_matrix_to_transform(float *p_js_matrix); void _update_tracker(int p_controller_id); + Vector2 _get_joy_vector_from_axes(int *p_axes); + int _get_touch_index(int p_input_source); + Vector2 _get_screen_position_from_joy_vector(const Vector2 &p_joy_vector); + public: virtual void is_session_supported(const String &p_session_mode) override; virtual void set_session_mode(String p_session_mode) override; @@ -74,6 +79,7 @@ class WebXRInterfaceJS : public WebXRInterface { void _set_reference_space_type(String p_reference_space_type); virtual String get_reference_space_type() const override; virtual Ref get_controller(int p_controller_id) const override; + virtual TargetRayMode get_controller_target_ray_mode(int p_controller_id) const override; virtual String get_visibility_state() const override; virtual PackedVector3Array get_bounds_geometry() const override; @@ -94,6 +100,7 @@ class WebXRInterfaceJS : public WebXRInterface { virtual void process() override; void _on_controller_changed(); + void _on_input_event(int p_event_type, int p_input_source); WebXRInterfaceJS(); ~WebXRInterfaceJS();