From 0b77271529f05786c4b2f5dd7000978768d186b1 Mon Sep 17 00:00:00 2001 From: Bastiaan Olij Date: Wed, 18 Sep 2024 10:14:21 +1000 Subject: [PATCH] OpenXR: Add support for binding modifiers --- doc/classes/ProjectSettings.xml | 6 + main/main.cpp | 4 + .../openxr/action_map/openxr_action_map.cpp | 35 ++- modules/openxr/action_map/openxr_action_map.h | 1 + .../action_map/openxr_binding_modifier.cpp | 34 +++ .../action_map/openxr_binding_modifier.h | 65 +++++ .../action_map/openxr_interaction_profile.cpp | 153 ++++++++++ .../action_map/openxr_interaction_profile.h | 35 ++- modules/openxr/config.py | 8 + .../OpenXRAnalogThresholdEditor.xml | 9 + .../OpenXRAnalogThresholdModifier.xml | 26 ++ .../doc_classes/OpenXRBindingModifier.xml | 11 + .../OpenXRBindingModifierEditor.xml | 19 ++ .../doc_classes/OpenXRDpadBindingEditor.xml | 9 + .../doc_classes/OpenXRDpadBindingModifier.xml | 35 +++ .../doc_classes/OpenXRInteractionProfile.xml | 15 + .../OpenXRInteractionProfileEditor.xml | 9 + .../OpenXRInteractionProfileEditorBase.xml | 9 + .../editor/openxr_action_map_editor.cpp | 49 +++- .../openxr/editor/openxr_action_map_editor.h | 8 + .../editor/openxr_binding_modifier_editor.cpp | 108 ++++++++ .../editor/openxr_binding_modifier_editor.h | 76 +++++ .../openxr/editor/openxr_editor_plugin.cpp | 3 + .../openxr_interaction_profile_editor.cpp | 196 ++++++++++++- .../openxr_interaction_profile_editor.h | 37 ++- .../openxr_dpad_binding_extension.cpp | 261 ++++++++++++++++++ .../openxr_dpad_binding_extension.h | 128 +++++++++ ...penxr_valve_analog_threshold_extension.cpp | 203 ++++++++++++++ .../openxr_valve_analog_threshold_extension.h | 113 ++++++++ modules/openxr/openxr_api.cpp | 120 +++++++- modules/openxr/openxr_api.h | 9 +- modules/openxr/openxr_interface.cpp | 10 + modules/openxr/register_types.cpp | 24 ++ 33 files changed, 1783 insertions(+), 45 deletions(-) create mode 100644 modules/openxr/action_map/openxr_binding_modifier.cpp create mode 100644 modules/openxr/action_map/openxr_binding_modifier.h create mode 100644 modules/openxr/doc_classes/OpenXRAnalogThresholdEditor.xml create mode 100644 modules/openxr/doc_classes/OpenXRAnalogThresholdModifier.xml create mode 100644 modules/openxr/doc_classes/OpenXRBindingModifier.xml create mode 100644 modules/openxr/doc_classes/OpenXRBindingModifierEditor.xml create mode 100644 modules/openxr/doc_classes/OpenXRDpadBindingEditor.xml create mode 100644 modules/openxr/doc_classes/OpenXRDpadBindingModifier.xml create mode 100644 modules/openxr/doc_classes/OpenXRInteractionProfileEditor.xml create mode 100644 modules/openxr/doc_classes/OpenXRInteractionProfileEditorBase.xml create mode 100644 modules/openxr/editor/openxr_binding_modifier_editor.cpp create mode 100644 modules/openxr/editor/openxr_binding_modifier_editor.h create mode 100644 modules/openxr/extensions/openxr_dpad_binding_extension.cpp create mode 100644 modules/openxr/extensions/openxr_dpad_binding_extension.h create mode 100644 modules/openxr/extensions/openxr_valve_analog_threshold_extension.cpp create mode 100644 modules/openxr/extensions/openxr_valve_analog_threshold_extension.h diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 4266bab2a175..8be3b8437d0b 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2952,6 +2952,12 @@ Maximum number of threads to be used by [WorkerThreadPool]. Value of [code]-1[/code] means no limit. + + Enables the analog threshold binding modifier if supported by the XR runtime. + + + Enabled the dpad binding modifier if supported by the XR runtime. + Action map configuration to load by default. diff --git a/main/main.cpp b/main/main.cpp index 5206e9b84c57..2d87ccde51f6 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2604,6 +2604,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph GLOBAL_DEF_RST_BASIC("xr/openxr/extensions/hand_interaction_profile", false); GLOBAL_DEF_BASIC("xr/openxr/extensions/eye_gaze_interaction", false); + // OpenXR Binding modifier settings + GLOBAL_DEF_BASIC("xr/openxr/binding_modifiers/analog_threshold", false); + GLOBAL_DEF_BASIC("xr/openxr/binding_modifiers/dpad_binding", false); + #ifdef TOOLS_ENABLED // Disabled for now, using XR inside of the editor we'll be working on during the coming months. diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp index d08c35b0a8bf..a0a6d9fedd5d 100644 --- a/modules/openxr/action_map/openxr_action_map.cpp +++ b/modules/openxr/action_map/openxr_action_map.cpp @@ -107,14 +107,26 @@ void OpenXRActionMap::remove_action_set(Ref p_action_set) { } } -void OpenXRActionMap::set_interaction_profiles(Array p_interaction_profiles) { +void OpenXRActionMap::clear_interaction_profiles() { + // Interaction profiles held within our action map set should be released and destroyed but just in case they are still used some where else + if (interaction_profiles.size() == 0) { + return; + } + + for (int i = 0; i < interaction_profiles.size(); i++) { + Ref interaction_profile = interaction_profiles[i]; + interaction_profile->action_map = nullptr; + } interaction_profiles.clear(); + emit_changed(); +} + +void OpenXRActionMap::set_interaction_profiles(Array p_interaction_profiles) { + clear_interaction_profiles(); for (int i = 0; i < p_interaction_profiles.size(); i++) { - Ref interaction_profile = p_interaction_profiles[i]; - if (interaction_profile.is_valid() && !interaction_profiles.has(interaction_profile)) { - interaction_profiles.push_back(interaction_profile); - } + // add them anew so we verify our interaction profile pointer + add_interaction_profile(p_interaction_profiles[i]); } } @@ -147,6 +159,13 @@ void OpenXRActionMap::add_interaction_profile(Ref p_in ERR_FAIL_COND(p_interaction_profile.is_null()); if (!interaction_profiles.has(p_interaction_profile)) { + if (p_interaction_profile->action_map && p_interaction_profile->action_map != this) { + // interaction profiles should only relate to our action map + p_interaction_profile->action_map->remove_interaction_profile(p_interaction_profile); + } + + p_interaction_profile->action_map = this; + interaction_profiles.push_back(p_interaction_profile); emit_changed(); } @@ -156,6 +175,10 @@ void OpenXRActionMap::remove_interaction_profile(Ref p int idx = interaction_profiles.find(p_interaction_profile); if (idx != -1) { interaction_profiles.remove_at(idx); + + ERR_FAIL_COND_MSG(p_interaction_profile->action_map != this, "Removing interaction profile that belongs to this action map but had incorrect action map pointer."); // this should never happen! + p_interaction_profile->action_map = nullptr; + emit_changed(); } } @@ -598,5 +621,5 @@ PackedStringArray OpenXRActionMap::get_top_level_paths(const Ref p OpenXRActionMap::~OpenXRActionMap() { action_sets.clear(); - interaction_profiles.clear(); + clear_interaction_profiles(); } diff --git a/modules/openxr/action_map/openxr_action_map.h b/modules/openxr/action_map/openxr_action_map.h index 678b3d7fbc57..7239a8534c9f 100644 --- a/modules/openxr/action_map/openxr_action_map.h +++ b/modules/openxr/action_map/openxr_action_map.h @@ -57,6 +57,7 @@ class OpenXRActionMap : public Resource { void add_action_set(Ref p_action_set); // Add an action set to our action map void remove_action_set(Ref p_action_set); // Remove an action set from our action map + void clear_interaction_profiles(); // Remove all our interaction profiles void set_interaction_profiles(Array p_interaction_profiles); // Set our interaction profiles by providing an array (for loading from resource) Array get_interaction_profiles() const; // Get our interaction profiles as an array (for saving to resource) diff --git a/modules/openxr/action_map/openxr_binding_modifier.cpp b/modules/openxr/action_map/openxr_binding_modifier.cpp new file mode 100644 index 000000000000..29ed90104fcf --- /dev/null +++ b/modules/openxr/action_map/openxr_binding_modifier.cpp @@ -0,0 +1,34 @@ +/**************************************************************************/ +/* openxr_binding_modifier.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_binding_modifier.h" + +void OpenXRBindingModifier::_bind_methods() { +} diff --git a/modules/openxr/action_map/openxr_binding_modifier.h b/modules/openxr/action_map/openxr_binding_modifier.h new file mode 100644 index 000000000000..c5a0d8b13069 --- /dev/null +++ b/modules/openxr/action_map/openxr_binding_modifier.h @@ -0,0 +1,65 @@ +/**************************************************************************/ +/* openxr_binding_modifier.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_BINDING_MODIFIER_H +#define OPENXR_BINDING_MODIFIER_H + +#include "../action_map/openxr_action.h" +#include "core/io/resource.h" + +// Part of implementation for: +// https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_KHR_binding_modification + +struct XrBindingModificationBaseHeaderKHR; +class OpenXRInteractionProfile; +class OpenXRIPBinding; + +class OpenXRBindingModifier : public Resource { + GDCLASS(OpenXRBindingModifier, Resource); + +private: +protected: + friend class OpenXRInteractionProfile; + friend class OpenXRIPBinding; + + static void _bind_methods(); + + OpenXRInteractionProfile *interaction_profile = nullptr; // action belongs to this interaction profile (should only be set if record_on_binding() == false). + OpenXRIPBinding *ip_binding = nullptr; // action belongs to this binding (should only be set if record_on_binding() == true). + +public: + virtual bool record_on_binding() const { return true; } // If true, this binding modifier is recorded on a specific binding. If false, this binding modifier is recorded on the interaction profile. + + virtual String get_description() const = 0; // Returns the description shown in the editor + virtual int get_binding_modification_struct_size() const = 0; // Return the size of the struct returned by get_binding_modification + virtual const XrBindingModificationBaseHeaderKHR *get_binding_modification() = 0; // Return the binding modifier struct used when calling xrSuggestInteractionProfileBindings +}; + +#endif // OPENXR_BINDING_MODIFIER_H diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp index 8e8aff859876..2971e6ee577d 100644 --- a/modules/openxr/action_map/openxr_interaction_profile.cpp +++ b/modules/openxr/action_map/openxr_interaction_profile.cpp @@ -39,6 +39,12 @@ void OpenXRIPBinding::_bind_methods() { ClassDB::bind_method(D_METHOD("get_source_path"), &OpenXRIPBinding::get_source_path); ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_path"), "set_source_path", "get_source_path"); + ClassDB::bind_method(D_METHOD("get_binding_modifier_count"), &OpenXRIPBinding::get_binding_modifier_count); + ClassDB::bind_method(D_METHOD("get_binding_modifier", "index"), &OpenXRIPBinding::get_binding_modifier); + ClassDB::bind_method(D_METHOD("set_binding_modifiers", "binding_modifiers"), &OpenXRIPBinding::set_binding_modifiers); + ClassDB::bind_method(D_METHOD("get_binding_modifiers"), &OpenXRIPBinding::get_binding_modifiers); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "binding_modifiers", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRBindingModifier", PROPERTY_USAGE_NO_EDITOR), "set_binding_modifiers", "get_binding_modifiers"); + // Deprecated ClassDB::bind_method(D_METHOD("set_paths", "paths"), &OpenXRIPBinding::set_paths); ClassDB::bind_method(D_METHOD("get_paths"), &OpenXRIPBinding::get_paths); @@ -78,6 +84,76 @@ String OpenXRIPBinding::get_source_path() const { return source_path; } +int OpenXRIPBinding::get_binding_modifier_count() const { + return binding_modifiers.size(); +} + +Ref OpenXRIPBinding::get_binding_modifier(int p_index) const { + ERR_FAIL_INDEX_V(p_index, binding_modifiers.size(), nullptr); + + return binding_modifiers[p_index]; +} + +void OpenXRIPBinding::clear_binding_modifiers() { + // Binding modifiers held within our interaction profile set should be released and destroyed but just in case they are still used some where else + if (binding_modifiers.size() == 0) { + return; + } + + for (int i = 0; i < binding_modifiers.size(); i++) { + Ref binding_modifier = binding_modifiers[i]; + binding_modifier->ip_binding = nullptr; + } + binding_modifiers.clear(); + emit_changed(); +} + +void OpenXRIPBinding::set_binding_modifiers(Array p_binding_modifiers) { + // Any binding modifier not retained in p_binding_modifiers should be freed automatically, those held within our Array will have be relinked to our interaction profile. + clear_binding_modifiers(); + + for (int i = 0; i < p_binding_modifiers.size(); i++) { + // add them anew so we verify our binding modifier pointer + add_binding_modifier(p_binding_modifiers[i]); + } +} + +Array OpenXRIPBinding::get_binding_modifiers() const { + Array ret; + for (const Ref &binding_modifier : binding_modifiers) { + ret.push_back(binding_modifier); + } + return ret; +} + +void OpenXRIPBinding::add_binding_modifier(Ref p_binding_modifier) { + ERR_FAIL_COND(p_binding_modifier.is_null()); + ERR_FAIL_COND_MSG(!p_binding_modifier->record_on_binding(), "This binding modifier must be added to an interaction profile."); + + if (!binding_modifiers.has(p_binding_modifier)) { + if (p_binding_modifier->ip_binding && p_binding_modifier->ip_binding != this) { + // binding modifier should only relate to our binding + p_binding_modifier->ip_binding->remove_binding_modifier(p_binding_modifier); + } + + p_binding_modifier->ip_binding = this; + binding_modifiers.push_back(p_binding_modifier); + emit_changed(); + } +} + +void OpenXRIPBinding::remove_binding_modifier(Ref p_binding_modifier) { + int idx = binding_modifiers.find(p_binding_modifier); + if (idx != -1) { + binding_modifiers.remove_at(idx); + + ERR_FAIL_COND_MSG(p_binding_modifier->ip_binding != this, "Removing binding modifier that belongs to this binding but had incorrect binding pointer."); // this should never happen! + p_binding_modifier->ip_binding = nullptr; + + emit_changed(); + } +} + void OpenXRIPBinding::set_paths(const PackedStringArray p_paths) { // deprecated // Fallback logic, this should ONLY be called when loading older action maps. // We'll parse this momentarily and extract individual bindings. @@ -141,6 +217,12 @@ void OpenXRInteractionProfile::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bindings", "bindings"), &OpenXRInteractionProfile::set_bindings); ClassDB::bind_method(D_METHOD("get_bindings"), &OpenXRInteractionProfile::get_bindings); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "bindings", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRIPBinding", PROPERTY_USAGE_NO_EDITOR), "set_bindings", "get_bindings"); + + ClassDB::bind_method(D_METHOD("get_binding_modifier_count"), &OpenXRInteractionProfile::get_binding_modifier_count); + ClassDB::bind_method(D_METHOD("get_binding_modifier", "index"), &OpenXRInteractionProfile::get_binding_modifier); + ClassDB::bind_method(D_METHOD("set_binding_modifiers", "binding_modifiers"), &OpenXRInteractionProfile::set_binding_modifiers); + ClassDB::bind_method(D_METHOD("get_binding_modifiers"), &OpenXRInteractionProfile::get_binding_modifiers); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "binding_modifiers", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRBindingModifier", PROPERTY_USAGE_NO_EDITOR), "set_binding_modifiers", "get_binding_modifiers"); } Ref OpenXRInteractionProfile::new_profile(const char *p_input_profile_path) { @@ -272,6 +354,77 @@ bool OpenXRInteractionProfile::has_binding_for_action(const Ref p_ return false; } +int OpenXRInteractionProfile::get_binding_modifier_count() const { + return binding_modifiers.size(); +} + +Ref OpenXRInteractionProfile::get_binding_modifier(int p_index) const { + ERR_FAIL_INDEX_V(p_index, binding_modifiers.size(), nullptr); + + return binding_modifiers[p_index]; +} + +void OpenXRInteractionProfile::clear_binding_modifiers() { + // Binding modifiers held within our interaction profile set should be released and destroyed but just in case they are still used some where else + if (binding_modifiers.size() == 0) { + return; + } + + for (int i = 0; i < binding_modifiers.size(); i++) { + Ref binding_modifier = binding_modifiers[i]; + binding_modifier->interaction_profile = nullptr; + } + binding_modifiers.clear(); + emit_changed(); +} + +void OpenXRInteractionProfile::set_binding_modifiers(Array p_binding_modifiers) { + // Any binding modifier not retained in p_binding_modifiers should be freed automatically, those held within our Array will have be relinked to our interaction profile. + clear_binding_modifiers(); + + for (int i = 0; i < p_binding_modifiers.size(); i++) { + // add them anew so we verify our binding modifier pointer + add_binding_modifier(p_binding_modifiers[i]); + } +} + +Array OpenXRInteractionProfile::get_binding_modifiers() const { + Array ret; + for (const Ref &binding_modifier : binding_modifiers) { + ret.push_back(binding_modifier); + } + return ret; +} + +void OpenXRInteractionProfile::add_binding_modifier(Ref p_binding_modifier) { + ERR_FAIL_COND(p_binding_modifier.is_null()); + ERR_FAIL_COND_MSG(p_binding_modifier->record_on_binding(), "This binding modifier must be added to a binding."); + + if (!binding_modifiers.has(p_binding_modifier)) { + if (p_binding_modifier->interaction_profile && p_binding_modifier->interaction_profile != this) { + // binding modifier should only relate to our interaction profile + p_binding_modifier->interaction_profile->remove_binding_modifier(p_binding_modifier); + } + + p_binding_modifier->interaction_profile = this; + binding_modifiers.push_back(p_binding_modifier); + emit_changed(); + } +} + +void OpenXRInteractionProfile::remove_binding_modifier(Ref p_binding_modifier) { + int idx = binding_modifiers.find(p_binding_modifier); + if (idx != -1) { + binding_modifiers.remove_at(idx); + + ERR_FAIL_COND_MSG(p_binding_modifier->interaction_profile != this, "Removing binding modifier that belongs to this interaction profile but had incorrect interaction profile pointer."); // this should never happen! + p_binding_modifier->interaction_profile = nullptr; + + emit_changed(); + } +} + OpenXRInteractionProfile::~OpenXRInteractionProfile() { bindings.clear(); + clear_binding_modifiers(); } diff --git a/modules/openxr/action_map/openxr_interaction_profile.h b/modules/openxr/action_map/openxr_interaction_profile.h index 59a93f17bbb2..5cafc9cba4a5 100644 --- a/modules/openxr/action_map/openxr_interaction_profile.h +++ b/modules/openxr/action_map/openxr_interaction_profile.h @@ -32,31 +32,48 @@ #define OPENXR_INTERACTION_PROFILE_H #include "openxr_action.h" +#include "openxr_binding_modifier.h" #include "openxr_interaction_profile_metadata.h" #include "core/io/resource.h" +class OpenXRActionMap; + class OpenXRIPBinding : public Resource { GDCLASS(OpenXRIPBinding, Resource); private: Ref action; String source_path; - - // PackedStringArray paths; + Vector> binding_modifiers; protected: + friend class OpenXRActionMap; + + OpenXRActionMap *action_map = nullptr; + static void _bind_methods(); public: static Ref new_binding(const Ref p_action, const String &p_source_path); // Helper function for adding a new binding + OpenXRActionMap *get_action_map() { return action_map; } // return the action map we're + void set_action(const Ref p_action); // Set the action for this binding Ref get_action() const; // Get the action for this binding void set_source_path(const String &path); String get_source_path() const; + int get_binding_modifier_count() const; // Retrieve the number of binding modifiers in this profile path + Ref get_binding_modifier(int p_index) const; + void clear_binding_modifiers(); // Remove all binding modifiers + void set_binding_modifiers(Array p_bindings); // Set the binding modifiers (for loading from a resource) + Array get_binding_modifiers() const; // Get the binding modifiers (for saving to a resource) + + void add_binding_modifier(Ref p_binding_modifier); // Add a binding modifier object + void remove_binding_modifier(Ref p_binding_modifier); // Remove a binding modifier object + // Deprecated void set_paths(const PackedStringArray p_paths); // Set our paths (for loading from resource) PackedStringArray get_paths() const; // Get our paths (for saving to resource) @@ -77,8 +94,13 @@ class OpenXRInteractionProfile : public Resource { private: String interaction_profile_path; Array bindings; + Vector> binding_modifiers; protected: + friend class OpenXRActionMap; + + OpenXRActionMap *action_map = nullptr; + static void _bind_methods(); public: @@ -101,6 +123,15 @@ class OpenXRInteractionProfile : public Resource { void remove_binding_for_action(const Ref p_action); // Remove all bindings for this action bool has_binding_for_action(const Ref p_action); // Returns true if we have a binding for this action + int get_binding_modifier_count() const; // Retrieve the number of binding modifiers in this profile path + Ref get_binding_modifier(int p_index) const; + void clear_binding_modifiers(); // Remove all binding modifiers + void set_binding_modifiers(Array p_bindings); // Set the binding modifiers (for loading from a resource) + Array get_binding_modifiers() const; // Get the binding modifiers (for saving to a resource) + + void add_binding_modifier(Ref p_binding_modifier); // Add a binding modifier object + void remove_binding_modifier(Ref p_binding_modifier); // Remove a binding modifier object + ~OpenXRInteractionProfile(); }; diff --git a/modules/openxr/config.py b/modules/openxr/config.py index 559aa2acf69c..a32e19cae226 100644 --- a/modules/openxr/config.py +++ b/modules/openxr/config.py @@ -27,6 +27,14 @@ def get_doc_classes(): "OpenXRCompositionLayerQuad", "OpenXRCompositionLayerCylinder", "OpenXRCompositionLayerEquirect", + "OpenXRBindingModifier", + "OpenXRAnalogThresholdModifier", + "OpenXRDpadBindingModifier", + "OpenXRInteractionProfileEditorBase", + "OpenXRInteractionProfileEditor", + "OpenXRBindingModifierEditor", + "OpenXRAnalogThresholdEditor", + "OpenXRDpadBindingEditor", ] diff --git a/modules/openxr/doc_classes/OpenXRAnalogThresholdEditor.xml b/modules/openxr/doc_classes/OpenXRAnalogThresholdEditor.xml new file mode 100644 index 000000000000..3ab58b8ad365 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRAnalogThresholdEditor.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/modules/openxr/doc_classes/OpenXRAnalogThresholdModifier.xml b/modules/openxr/doc_classes/OpenXRAnalogThresholdModifier.xml new file mode 100644 index 000000000000..e5a9d5aa2384 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRAnalogThresholdModifier.xml @@ -0,0 +1,26 @@ + + + + The analog threshold binding modifier can modify a float input to a boolean input with specified thresholds. + + + The analog threshold binding modifier can modify a float input to a boolean input with specified thresholds. + See [url=https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_VALVE_analog_threshold]XR_VALVE_analog_threshold[/url] for indepth details. + + + + + + Action related to this analog threshold modifier. + + + Input path related to this analog threshold modifier. + + + When our input value falls below this, our output becomes false. + + + When our input value is equal or larger than this value, our output becomes true. It stays true until it falls under the [member off_threshold] value. + + + diff --git a/modules/openxr/doc_classes/OpenXRBindingModifier.xml b/modules/openxr/doc_classes/OpenXRBindingModifier.xml new file mode 100644 index 000000000000..12af05ed9d74 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRBindingModifier.xml @@ -0,0 +1,11 @@ + + + + Binding modifier base class. + + + Binding modifier base class. Subclasses implement various modifiers that alter how an OpenXR runtime processes inputs. + + + + diff --git a/modules/openxr/doc_classes/OpenXRBindingModifierEditor.xml b/modules/openxr/doc_classes/OpenXRBindingModifierEditor.xml new file mode 100644 index 000000000000..813c9cc6c2bb --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRBindingModifierEditor.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/modules/openxr/doc_classes/OpenXRDpadBindingEditor.xml b/modules/openxr/doc_classes/OpenXRDpadBindingEditor.xml new file mode 100644 index 000000000000..4e0b826e1b61 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRDpadBindingEditor.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/modules/openxr/doc_classes/OpenXRDpadBindingModifier.xml b/modules/openxr/doc_classes/OpenXRDpadBindingModifier.xml new file mode 100644 index 000000000000..1b54ef171087 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRDpadBindingModifier.xml @@ -0,0 +1,35 @@ + + + + The DPad binding modifier converts an axis input to a dpad output. + + + The DPad binding modifier converts an axis input to a dpad output. New input paths for each dpad direction will be added to the interaction profile. + See [url=https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_EXT_dpad_binding]XR_EXT_dpad_binding[/url] for indepth details. + + + + + + Action set for which this dpad binding modifier is active. + + + Center region in which our center position of our dpad return [code]true[/code]. + + + Input path for this dpad binding modifier. + + + If [code]false[/code], when the joystick enters a new dpad zone this becomes true. + If [code]true[/code], when the joystick remains in active dpad zone, this remains true even if we overlap with another zone. + + + When our input value is equal or larger than this value, our dpad in that direction becomes true. It stays true until it falls under the [member threshold_released] value. + + + When our input value falls below this, our output becomes false. + + + + + diff --git a/modules/openxr/doc_classes/OpenXRInteractionProfile.xml b/modules/openxr/doc_classes/OpenXRInteractionProfile.xml index ed5113f83c9d..364508ef9989 100644 --- a/modules/openxr/doc_classes/OpenXRInteractionProfile.xml +++ b/modules/openxr/doc_classes/OpenXRInteractionProfile.xml @@ -23,8 +23,23 @@ Get the number of bindings in this interaction profile. + + + + + Get the [OpenXRBindingModifier] at this index. + + + + + + Get the number of binding modifiers in this interaction profile. + + + + Action bindings for this interaction profile. diff --git a/modules/openxr/doc_classes/OpenXRInteractionProfileEditor.xml b/modules/openxr/doc_classes/OpenXRInteractionProfileEditor.xml new file mode 100644 index 000000000000..05c431000231 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRInteractionProfileEditor.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/modules/openxr/doc_classes/OpenXRInteractionProfileEditorBase.xml b/modules/openxr/doc_classes/OpenXRInteractionProfileEditorBase.xml new file mode 100644 index 000000000000..430380a679a4 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRInteractionProfileEditorBase.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/modules/openxr/editor/openxr_action_map_editor.cpp b/modules/openxr/editor/openxr_action_map_editor.cpp index a353073f215b..3688c3734992 100644 --- a/modules/openxr/editor/openxr_action_map_editor.cpp +++ b/modules/openxr/editor/openxr_action_map_editor.cpp @@ -37,7 +37,8 @@ #include "editor/gui/editor_file_dialog.h" #include "editor/themes/editor_scale.h" -// TODO implement redo/undo system +HashMap OpenXRActionMapEditor::interaction_profile_editors; +HashMap OpenXRActionMapEditor::binding_modifier_editors; void OpenXRActionMapEditor::_bind_methods() { ClassDB::bind_method("_add_action_set_editor", &OpenXRActionMapEditor::_add_action_set_editor); @@ -50,6 +51,9 @@ void OpenXRActionMapEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_do_remove_action_set_editor", "action_set_editor"), &OpenXRActionMapEditor::_do_remove_action_set_editor); ClassDB::bind_method(D_METHOD("_do_add_interaction_profile_editor", "interaction_profile_editor"), &OpenXRActionMapEditor::_do_add_interaction_profile_editor); ClassDB::bind_method(D_METHOD("_do_remove_interaction_profile_editor", "interaction_profile_editor"), &OpenXRActionMapEditor::_do_remove_interaction_profile_editor); + + ClassDB::bind_static_method("OpenXRActionMapEditor", D_METHOD("register_interaction_profile_editor", "interaction_profile_path", "editor_class"), &OpenXRActionMapEditor::register_interaction_profile_editor); + ClassDB::bind_static_method("OpenXRActionMapEditor", D_METHOD("register_binding_modifier_editor", "binding_modifier_class", "editor_class"), &OpenXRActionMapEditor::register_binding_modifier_editor); } void OpenXRActionMapEditor::_notification(int p_what) { @@ -100,9 +104,17 @@ OpenXRInteractionProfileEditorBase *OpenXRActionMapEditor::_add_interaction_prof // need to instance the correct editor for our profile OpenXRInteractionProfileEditorBase *new_profile_editor = nullptr; - if (profile_path == "placeholder_text") { - // instance specific editor for this type - } else { + if (interaction_profile_editors.has(profile_path)) { + Object *new_editor = ClassDB::instantiate(interaction_profile_editors[profile_path]); + if (new_editor) { + new_profile_editor = Object::cast_to(new_editor); + if (!new_profile_editor) { + WARN_PRINT("Interaction profile editor type mismatch for " + profile_path); + memfree(new_editor); + } + } + } + if (!new_profile_editor) { // instance generic editor new_profile_editor = memnew(OpenXRInteractionProfileEditor(action_map, p_interaction_profile)); } @@ -195,8 +207,19 @@ void OpenXRActionMapEditor::_on_remove_action_set(Object *p_action_set_editor) { Ref action_set = action_set_editor->get_action_set(); ERR_FAIL_COND(action_set.is_null()); + // Remove all actions first. action_set_editor->remove_all_actions(); + // Make sure we update our interaction profiles. + for (int i = 0; i < tabs->get_tab_count(); i++) { + // First tab won't be an interaction profile editor, but being thorough.. + OpenXRInteractionProfileEditorBase *interaction_profile_editor = Object::cast_to(tabs->get_tab_control(i)); + if (interaction_profile_editor) { + interaction_profile_editor->remove_all_for_action_set(action_set); + } + } + + // And now we can remove our action set. undo_redo->create_action(TTR("Remove action set")); undo_redo->add_do_method(this, "_do_remove_action_set_editor", action_set_editor); undo_redo->add_undo_method(this, "_do_add_action_set_editor", action_set_editor); @@ -210,7 +233,7 @@ void OpenXRActionMapEditor::_on_action_removed(Ref p_action) { // First tab won't be an interaction profile editor, but being thorough.. OpenXRInteractionProfileEditorBase *interaction_profile_editor = Object::cast_to(tabs->get_tab_control(i)); if (interaction_profile_editor) { - interaction_profile_editor->remove_all_bindings_for_action(p_action); + interaction_profile_editor->remove_all_for_action(p_action); } } } @@ -387,6 +410,22 @@ void OpenXRActionMapEditor::_clear_action_map() { } } +void OpenXRActionMapEditor::register_interaction_profile_editor(const String &p_for_path, const String &p_editor_class) { + interaction_profile_editors[p_for_path] = p_editor_class; +} + +void OpenXRActionMapEditor::register_binding_modifier_editor(const String &p_binding_modifier_class, const String &p_editor_class) { + binding_modifier_editors[p_binding_modifier_class] = p_editor_class; +} + +String OpenXRActionMapEditor::get_binding_modifier_editor_class(const String &p_binding_modifier_class) { + if (binding_modifier_editors.has(p_binding_modifier_class)) { + return binding_modifier_editors[p_binding_modifier_class]; + } + + return ""; +} + OpenXRActionMapEditor::OpenXRActionMapEditor() { undo_redo = EditorUndoRedoManager::get_singleton(); set_custom_minimum_size(Size2(0.0, 300.0)); diff --git a/modules/openxr/editor/openxr_action_map_editor.h b/modules/openxr/editor/openxr_action_map_editor.h index cfe5fed0955e..088bf0c613d0 100644 --- a/modules/openxr/editor/openxr_action_map_editor.h +++ b/modules/openxr/editor/openxr_action_map_editor.h @@ -36,6 +36,7 @@ #include "openxr_interaction_profile_editor.h" #include "openxr_select_interaction_profile_dialog.h" +#include "core/templates/hash_map.h" #include "editor/editor_undo_redo_manager.h" #include "editor/plugins/editor_plugin.h" #include "scene/gui/box_container.h" @@ -48,6 +49,9 @@ class OpenXRActionMapEditor : public VBoxContainer { GDCLASS(OpenXRActionMapEditor, VBoxContainer); private: + static HashMap interaction_profile_editors; // interaction profile path, interaction profile editor + static HashMap binding_modifier_editors; // binding modifier class, binding modifiers editor + EditorUndoRedoManager *undo_redo; String edited_path; Ref action_map; @@ -100,6 +104,10 @@ class OpenXRActionMapEditor : public VBoxContainer { void _do_remove_interaction_profile_editor(OpenXRInteractionProfileEditorBase *p_interaction_profile_editor); public: + static void register_interaction_profile_editor(const String &p_for_path, const String &p_editor_class); + static void register_binding_modifier_editor(const String &p_binding_modifier_class, const String &p_editor_class); + static String get_binding_modifier_editor_class(const String &p_binding_modifier_class); + void open_action_map(String p_path); OpenXRActionMapEditor(); diff --git a/modules/openxr/editor/openxr_binding_modifier_editor.cpp b/modules/openxr/editor/openxr_binding_modifier_editor.cpp new file mode 100644 index 000000000000..d9da077b0c38 --- /dev/null +++ b/modules/openxr/editor/openxr_binding_modifier_editor.cpp @@ -0,0 +1,108 @@ +/**************************************************************************/ +/* openxr_binding_modifier_editor.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_binding_modifier_editor.h" + +#include "editor/editor_string_names.h" + +void OpenXRBindingModifierEditor::_bind_methods() { + ClassDB::bind_method("_on_remove_binding_modifier", &OpenXRBindingModifierEditor::_on_remove_binding_modifier); + + ADD_SIGNAL(MethodInfo("remove", PropertyInfo(Variant::OBJECT, "binding_modifier_editor"))); +} + +void OpenXRBindingModifierEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + rem_binding_modifier_btn->set_icon(get_theme_icon(SNAME("Remove"), EditorStringName(EditorIcons))); + } break; + } +} + +void OpenXRBindingModifierEditor::_on_remove_binding_modifier() { + // Tell parent to remove us + emit_signal("remove", this); +} + +void OpenXRBindingModifierEditor::add_property_editor(const String &p_property, EditorProperty *p_editor) { + p_editor->set_label(p_property.capitalize()); + p_editor->connect("property_changed", callable_mp(this, &OpenXRBindingModifierEditor::_on_property_changed)); + main_vb->add_child(p_editor); + property_editors[StringName(p_property)] = p_editor; +} + +void OpenXRBindingModifierEditor::_on_property_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) { + ERR_FAIL_NULL(undo_redo); + ERR_FAIL_COND(binding_modifier.is_null()); + + undo_redo->create_action(vformat(TTR("Modify '%s' for binding modifier '%s'"), p_property, binding_modifier->get_description())); + undo_redo->add_do_property(binding_modifier.ptr(), p_property, p_value); + undo_redo->add_do_method(property_editors[p_property], "update_property"); + undo_redo->add_undo_property(binding_modifier.ptr(), p_property, binding_modifier->get(p_property)); + undo_redo->add_undo_method(property_editors[p_property], "update_property"); + undo_redo->commit_action(); +} + +OpenXRBindingModifierEditor::OpenXRBindingModifierEditor() { + undo_redo = EditorUndoRedoManager::get_singleton(); + + set_h_size_flags(Control::SIZE_EXPAND_FILL); + + main_vb = memnew(VBoxContainer); + main_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + add_child(main_vb); + + header_hb = memnew(HBoxContainer); + header_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + main_vb->add_child(header_hb); + + binding_modifier_title = memnew(Label); + binding_modifier_title->set_h_size_flags(Control::SIZE_EXPAND_FILL); + header_hb->add_child(binding_modifier_title); + + rem_binding_modifier_btn = memnew(Button); + rem_binding_modifier_btn->set_tooltip_text(TTR("Remove binding modifier.")); + rem_binding_modifier_btn->connect(SceneStringName(pressed), callable_mp(this, &OpenXRBindingModifierEditor::_on_remove_binding_modifier)); + rem_binding_modifier_btn->set_flat(true); + header_hb->add_child(rem_binding_modifier_btn); +} + +void OpenXRBindingModifierEditor::set_binding_modifier(Ref p_action_map, Ref p_binding_modifier) { + action_map = p_action_map; + binding_modifier = p_binding_modifier; + + binding_modifier_title->set_text(p_binding_modifier->get_description()); + + for (const KeyValue &editor : property_editors) { + editor.value->set_object_and_property(binding_modifier.ptr(), editor.key); + editor.value->update_property(); + } +} diff --git a/modules/openxr/editor/openxr_binding_modifier_editor.h b/modules/openxr/editor/openxr_binding_modifier_editor.h new file mode 100644 index 000000000000..8d9576b051c6 --- /dev/null +++ b/modules/openxr/editor/openxr_binding_modifier_editor.h @@ -0,0 +1,76 @@ +/**************************************************************************/ +/* openxr_binding_modifier_editor.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_BINDING_MODIFIER_EDITOR_H +#define OPENXR_BINDING_MODIFIER_EDITOR_H + +#include "../action_map/openxr_action_map.h" +#include "../action_map/openxr_binding_modifier.h" +#include "editor/editor_properties.h" +#include "editor/editor_undo_redo_manager.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/label.h" +#include "scene/gui/panel_container.h" + +class OpenXRBindingModifierEditor : public PanelContainer { + GDCLASS(OpenXRBindingModifierEditor, PanelContainer); + +private: + HBoxContainer *header_hb = nullptr; + Label *binding_modifier_title = nullptr; + Button *rem_binding_modifier_btn = nullptr; + +protected: + VBoxContainer *main_vb = nullptr; + HashMap property_editors; + + EditorUndoRedoManager *undo_redo; + Ref binding_modifier; + Ref action_map; + + static void _bind_methods(); + void _notification(int p_what); + + void add_property_editor(const String &p_property, EditorProperty *p_editor); + + void _on_remove_binding_modifier(); + +public: + Ref get_binding_modifier() const { return binding_modifier; } + + void _on_property_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing); + + virtual void set_binding_modifier(Ref p_action_map, Ref p_binding_modifier); + + OpenXRBindingModifierEditor(); +}; + +#endif // OPENXR_BINDING_MODIFIER_EDITOR_H diff --git a/modules/openxr/editor/openxr_editor_plugin.cpp b/modules/openxr/editor/openxr_editor_plugin.cpp index f6b7f2dd0c30..477dd9b01460 100644 --- a/modules/openxr/editor/openxr_editor_plugin.cpp +++ b/modules/openxr/editor/openxr_editor_plugin.cpp @@ -53,6 +53,9 @@ void OpenXREditorPlugin::make_visible(bool p_visible) { } OpenXREditorPlugin::OpenXREditorPlugin() { + OpenXRActionMapEditor::register_binding_modifier_editor("OpenXRAnalogThresholdModifier", "OpenXRAnalogThresholdEditor"); + OpenXRActionMapEditor::register_binding_modifier_editor("OpenXRDpadBindingModifier", "OpenXRDpadBindingEditor"); + action_map_editor = memnew(OpenXRActionMapEditor); EditorNode::get_bottom_panel()->add_item(TTR("OpenXR Action Map"), action_map_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_openxr_action_map_bottom_panel", TTR("Toggle OpenXR Action Map Bottom Panel"))); diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.cpp b/modules/openxr/editor/openxr_interaction_profile_editor.cpp index d23a9c315252..168478814315 100644 --- a/modules/openxr/editor/openxr_interaction_profile_editor.cpp +++ b/modules/openxr/editor/openxr_interaction_profile_editor.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "openxr_interaction_profile_editor.h" +#include "openxr_action_map_editor.h" #include "editor/editor_string_names.h" #include "scene/gui/box_container.h" @@ -45,6 +46,12 @@ void OpenXRInteractionProfileEditorBase::_bind_methods() { ClassDB::bind_method(D_METHOD("_add_binding", "action", "path"), &OpenXRInteractionProfileEditorBase::_add_binding); ClassDB::bind_method(D_METHOD("_remove_binding", "action", "path"), &OpenXRInteractionProfileEditorBase::_remove_binding); + + ClassDB::bind_method(D_METHOD("_on_add_binding_modifier"), &OpenXRInteractionProfileEditorBase::_on_add_binding_modifier); + ClassDB::bind_method(D_METHOD("_on_dialog_created"), &OpenXRInteractionProfileEditorBase::_on_dialog_created); + + ClassDB::bind_method(D_METHOD("_do_add_binding_modifier_editor", "binding_modifier_editor"), &OpenXRInteractionProfileEditorBase::_do_add_binding_modifier_editor); + ClassDB::bind_method(D_METHOD("_do_remove_binding_modifier_editor", "binding_modifier_editor"), &OpenXRInteractionProfileEditorBase::_do_remove_binding_modifier_editor); } void OpenXRInteractionProfileEditorBase::_notification(int p_what) { @@ -53,6 +60,10 @@ void OpenXRInteractionProfileEditorBase::_notification(int p_what) { _update_interaction_profile(); } break; + case NOTIFICATION_READY: { + _create_binding_modifiers(); + } break; + case NOTIFICATION_THEME_CHANGED: { _theme_changed(); } break; @@ -112,7 +123,31 @@ void OpenXRInteractionProfileEditorBase::_remove_binding(const String p_action, } } -void OpenXRInteractionProfileEditorBase::remove_all_bindings_for_action(Ref p_action) { +void OpenXRInteractionProfileEditorBase::_update_interaction_profile() { + if (!is_dirty) { + // no need to update + return; + } + + // Nothing to do here for now.. + + // and we've updated it... + is_dirty = false; +} + +void OpenXRInteractionProfileEditorBase::_theme_changed() { + if (binding_modifier_sc) { + binding_modifier_sc->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree"))); + } +} + +void OpenXRInteractionProfileEditorBase::remove_all_for_action_set(Ref p_action_set) { + // Note, don't need to remove bindings themselves as remove_all_for_action will be called for each + + // TODO update binding modifiers +} + +void OpenXRInteractionProfileEditorBase::remove_all_for_action(Ref p_action) { Vector> bindings = interaction_profile->get_bindings_for_action(p_action); if (bindings.size() > 0) { String action_name = p_action->get_name_with_set(); @@ -136,11 +171,143 @@ void OpenXRInteractionProfileEditorBase::remove_all_bindings_for_action(Ref p_binding_modifier) { + ERR_FAIL_COND_V(p_binding_modifier.is_null(), nullptr); + + String class_name = p_binding_modifier->get_class(); + ERR_FAIL_COND_V(class_name.is_empty(), nullptr); + String editor_class = OpenXRActionMapEditor::get_binding_modifier_editor_class(class_name); + ERR_FAIL_COND_V(editor_class.is_empty(), nullptr); + + OpenXRBindingModifierEditor *new_editor = nullptr; + + Object *obj = ClassDB::instantiate(editor_class); + if (obj) { + new_editor = Object::cast_to(obj); + if (!new_editor) { + // Not of correct type?? Free it. + memfree(obj); + } + } + ERR_FAIL_NULL_V(new_editor, nullptr); + + new_editor->set_binding_modifier(action_map, p_binding_modifier); + new_editor->connect("remove", callable_mp(this, &OpenXRInteractionProfileEditorBase::_on_remove_binding_modifier)); + + binding_modifiers_vb->add_child(new_editor); + new_editor->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree"))); + + return new_editor; +} + +void OpenXRInteractionProfileEditorBase::_create_binding_modifiers() { + if (interaction_profile.is_valid()) { + Array new_binding_modifiers = interaction_profile->get_binding_modifiers(); + for (int i = 0; i < new_binding_modifiers.size(); i++) { + Ref binding_modifier = new_binding_modifiers[i]; + _add_binding_modifier_editor(binding_modifier); + } + } +} + +void OpenXRInteractionProfileEditorBase::_on_add_binding_modifier() { + create_dialog->popup_create(false); +} + +void OpenXRInteractionProfileEditorBase::_on_remove_binding_modifier(Object *p_binding_modifier_editor) { + ERR_FAIL_COND(interaction_profile.is_null()); + + OpenXRBindingModifierEditor *binding_modifier_editor = Object::cast_to(p_binding_modifier_editor); + ERR_FAIL_NULL(binding_modifier_editor); + ERR_FAIL_COND(binding_modifier_editor->get_parent() != binding_modifiers_vb); + + undo_redo->create_action(TTR("Remove binding modifier")); + undo_redo->add_do_method(this, "_do_remove_binding_modifier_editor", binding_modifier_editor); + undo_redo->add_undo_method(this, "_do_add_binding_modifier_editor", binding_modifier_editor); + undo_redo->commit_action(true); + + interaction_profile->set_edited(true); +} + +void OpenXRInteractionProfileEditorBase::_on_dialog_created() { + // Instance new binding modifier object + Variant obj = create_dialog->instantiate_selected(); + ERR_FAIL_COND(obj.get_type() != Variant::OBJECT); + + Ref new_binding_modifier = obj; + ERR_FAIL_COND(new_binding_modifier.is_null()); + + // Add it to our interaction profile + interaction_profile->add_binding_modifier(new_binding_modifier); + interaction_profile->set_edited(true); + + // Create our editor for this + OpenXRBindingModifierEditor *binding_modifier_editor = _add_binding_modifier_editor(new_binding_modifier); + ERR_FAIL_NULL(binding_modifier_editor); + + // Add undo/redo + undo_redo->create_action(TTR("Add binding modifier")); + undo_redo->add_do_method(this, "_do_add_binding_modifier_editor", binding_modifier_editor); + undo_redo->add_undo_method(this, "_do_remove_binding_modifier_editor", binding_modifier_editor); + undo_redo->commit_action(false); +} + +void OpenXRInteractionProfileEditorBase::_do_add_binding_modifier_editor(OpenXRBindingModifierEditor *p_binding_modifier_editor) { + Ref binding_modifier = p_binding_modifier_editor->get_binding_modifier(); + ERR_FAIL_COND(binding_modifier.is_null()); + + interaction_profile->add_binding_modifier(binding_modifier); + binding_modifiers_vb->add_child(p_binding_modifier_editor); +} + +void OpenXRInteractionProfileEditorBase::_do_remove_binding_modifier_editor(OpenXRBindingModifierEditor *p_binding_modifier_editor) { + Ref binding_modifier = p_binding_modifier_editor->get_binding_modifier(); + ERR_FAIL_COND(binding_modifier.is_null()); + + binding_modifiers_vb->remove_child(p_binding_modifier_editor); + interaction_profile->remove_binding_modifier(binding_modifier); +} + +OpenXRInteractionProfileEditorBase::OpenXRInteractionProfileEditorBase() { + // Note, this class is not normally instantiated this way except when Godot does introspection. } OpenXRInteractionProfileEditorBase::OpenXRInteractionProfileEditorBase(Ref p_action_map, Ref p_interaction_profile) { undo_redo = EditorUndoRedoManager::get_singleton(); + set_h_size_flags(SIZE_EXPAND_FILL); + set_v_size_flags(SIZE_EXPAND_FILL); + + interaction_profile_sc = memnew(ScrollContainer); + interaction_profile_sc->set_h_size_flags(SIZE_EXPAND_FILL); + interaction_profile_sc->set_v_size_flags(SIZE_EXPAND_FILL); + add_child(interaction_profile_sc); + + binding_modifier_sc = memnew(ScrollContainer); + binding_modifier_sc->set_custom_minimum_size(Size2(350.0, 0.0)); + binding_modifier_sc->set_v_size_flags(SIZE_EXPAND_FILL); + binding_modifier_sc->set_v_size_flags(SIZE_EXPAND_FILL); + binding_modifier_sc->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + add_child(binding_modifier_sc); + + binding_modifiers_vb = memnew(VBoxContainer); + binding_modifiers_vb->set_v_size_flags(SIZE_EXPAND_FILL); + binding_modifier_sc->add_child(binding_modifiers_vb); + + add_binding_modifier_btn = memnew(Button); + add_binding_modifier_btn->set_text(TTR("Add binding modifier")); + add_binding_modifier_btn->connect("pressed", callable_mp(this, &OpenXRInteractionProfileEditorBase::_on_add_binding_modifier)); + binding_modifiers_vb->add_child(add_binding_modifier_btn); + + create_dialog = memnew(CreateDialog); + create_dialog->set_base_type("OpenXRBindingModifier"); + create_dialog->connect("create", callable_mp(this, &OpenXRInteractionProfileEditorBase::_on_dialog_created)); + add_child(create_dialog); + action_map = p_action_map; interaction_profile = p_interaction_profile; String profile_path = interaction_profile->get_interaction_profile_path(); @@ -152,8 +319,6 @@ OpenXRInteractionProfileEditorBase::OpenXRInteractionProfileEditorBase(Refget_child_count() > 0) { - memdelete(main_hb->get_child(0)); + while (interaction_profile_hb->get_child_count() > 0) { + memdelete(interaction_profile_hb->get_child(0)); } // in with the new... @@ -282,7 +447,7 @@ void OpenXRInteractionProfileEditor::_update_interaction_profile() { for (int i = 0; i < top_level_paths.size(); i++) { PanelContainer *panel = memnew(PanelContainer); panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); - main_hb->add_child(panel); + interaction_profile_hb->add_child(panel); panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("TabContainer"))); VBoxContainer *container = memnew(VBoxContainer); @@ -300,23 +465,30 @@ void OpenXRInteractionProfileEditor::_update_interaction_profile() { } } - // and we've updated it... - is_dirty = false; + OpenXRInteractionProfileEditorBase::_update_interaction_profile(); } void OpenXRInteractionProfileEditor::_theme_changed() { - for (int i = 0; i < main_hb->get_child_count(); i++) { - Control *panel = Object::cast_to(main_hb->get_child(i)); + OpenXRInteractionProfileEditorBase::_theme_changed(); + + interaction_profile_sc->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree"))); + + for (int i = 0; i < interaction_profile_hb->get_child_count(); i++) { + Control *panel = Object::cast_to(interaction_profile_hb->get_child(i)); if (panel) { panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("TabContainer"))); } } } +OpenXRInteractionProfileEditor::OpenXRInteractionProfileEditor() { + // Note, this class is not normally instantiated this way except when Godot does introspection. +} + OpenXRInteractionProfileEditor::OpenXRInteractionProfileEditor(Ref p_action_map, Ref p_interaction_profile) : OpenXRInteractionProfileEditorBase(p_action_map, p_interaction_profile) { - main_hb = memnew(HBoxContainer); - add_child(main_hb); + interaction_profile_hb = memnew(HBoxContainer); + interaction_profile_sc->add_child(interaction_profile_hb); select_action_dialog = memnew(OpenXRSelectActionDialog(p_action_map)); select_action_dialog->connect("action_selected", callable_mp(this, &OpenXRInteractionProfileEditor::action_selected)); diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.h b/modules/openxr/editor/openxr_interaction_profile_editor.h index 2ec72127cfcf..77e08c5737f2 100644 --- a/modules/openxr/editor/openxr_interaction_profile_editor.h +++ b/modules/openxr/editor/openxr_interaction_profile_editor.h @@ -34,19 +34,36 @@ #include "../action_map/openxr_action_map.h" #include "../action_map/openxr_interaction_profile.h" #include "../action_map/openxr_interaction_profile_metadata.h" +#include "../editor/openxr_binding_modifier_editor.h" #include "openxr_select_action_dialog.h" +#include "editor/create_dialog.h" #include "editor/editor_undo_redo_manager.h" #include "scene/gui/scroll_container.h" -class OpenXRInteractionProfileEditorBase : public ScrollContainer { - GDCLASS(OpenXRInteractionProfileEditorBase, ScrollContainer); +class OpenXRInteractionProfileEditorBase : public HBoxContainer { + GDCLASS(OpenXRInteractionProfileEditorBase, HBoxContainer); + +private: + ScrollContainer *binding_modifier_sc = nullptr; + VBoxContainer *binding_modifiers_vb = nullptr; + Button *add_binding_modifier_btn = nullptr; + CreateDialog *create_dialog = nullptr; + + OpenXRBindingModifierEditor *_add_binding_modifier_editor(Ref p_binding_modifier); + void _create_binding_modifiers(); + + void _on_add_binding_modifier(); + void _on_remove_binding_modifier(Object *p_binding_modifier_editor); + void _on_dialog_created(); protected: EditorUndoRedoManager *undo_redo; Ref interaction_profile; Ref action_map; + ScrollContainer *interaction_profile_sc = nullptr; + bool is_dirty = false; static void _bind_methods(); @@ -54,18 +71,24 @@ class OpenXRInteractionProfileEditorBase : public ScrollContainer { const OpenXRInteractionProfileMetadata::InteractionProfile *profile_def = nullptr; + // used for undo/redo + void _do_add_binding_modifier_editor(OpenXRBindingModifierEditor *p_binding_modifier_editor); + void _do_remove_binding_modifier_editor(OpenXRBindingModifierEditor *p_binding_modifier_editor); + public: Ref get_interaction_profile() { return interaction_profile; } - virtual void _update_interaction_profile() {} - virtual void _theme_changed() {} + virtual void _update_interaction_profile(); + virtual void _theme_changed(); void _do_update_interaction_profile(); void _add_binding(const String p_action, const String p_path); void _remove_binding(const String p_action, const String p_path); - void remove_all_bindings_for_action(Ref p_action); + void remove_all_for_action_set(Ref p_action_set); + void remove_all_for_action(Ref p_action); + OpenXRInteractionProfileEditorBase(); OpenXRInteractionProfileEditorBase(Ref p_action_map, Ref p_interaction_profile); }; @@ -74,7 +97,8 @@ class OpenXRInteractionProfileEditor : public OpenXRInteractionProfileEditorBase private: String selecting_for_io_path; - HBoxContainer *main_hb = nullptr; + HBoxContainer *interaction_profile_hb = nullptr; + OpenXRSelectActionDialog *select_action_dialog = nullptr; void _add_io_path(VBoxContainer *p_container, const OpenXRInteractionProfileMetadata::IOPath *p_io_path); @@ -86,6 +110,7 @@ class OpenXRInteractionProfileEditor : public OpenXRInteractionProfileEditorBase virtual void _update_interaction_profile() override; virtual void _theme_changed() override; + OpenXRInteractionProfileEditor(); OpenXRInteractionProfileEditor(Ref p_action_map, Ref p_interaction_profile); }; diff --git a/modules/openxr/extensions/openxr_dpad_binding_extension.cpp b/modules/openxr/extensions/openxr_dpad_binding_extension.cpp new file mode 100644 index 000000000000..1a7015d5d7b9 --- /dev/null +++ b/modules/openxr/extensions/openxr_dpad_binding_extension.cpp @@ -0,0 +1,261 @@ +/**************************************************************************/ +/* openxr_dpad_binding_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_dpad_binding_extension.h" +#include "../openxr_api.h" +#include "core/math/math_funcs.h" + +// Implementation for: +// https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_EXT_dpad_binding + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// OpenXRDPadBindingExtension + +OpenXRDPadBindingExtension *OpenXRDPadBindingExtension::singleton = nullptr; + +OpenXRDPadBindingExtension *OpenXRDPadBindingExtension::get_singleton() { + return singleton; +} + +OpenXRDPadBindingExtension::OpenXRDPadBindingExtension() { + singleton = this; +} + +OpenXRDPadBindingExtension::~OpenXRDPadBindingExtension() { + singleton = nullptr; +} + +HashMap OpenXRDPadBindingExtension::get_requested_extensions() { + HashMap request_extensions; + + // Note, we're dependent on the binding modifier extension, this may be requested by multiple extension wrappers. + request_extensions[XR_KHR_BINDING_MODIFICATION_EXTENSION_NAME] = &binding_modifier_ext; + request_extensions[XR_EXT_DPAD_BINDING_EXTENSION_NAME] = &dpad_binding_ext; + + return request_extensions; +} + +bool OpenXRDPadBindingExtension::is_available() { + return binding_modifier_ext && dpad_binding_ext; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// OpenXRDpadBindingModifier + +void OpenXRDpadBindingModifier::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_action_set", "action_set"), &OpenXRDpadBindingModifier::set_action_set); + ClassDB::bind_method(D_METHOD("get_action_set"), &OpenXRDpadBindingModifier::get_action_set); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "action_set", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRActionSet"), "set_action_set", "get_action_set"); + + ClassDB::bind_method(D_METHOD("set_input_path", "input_path"), &OpenXRDpadBindingModifier::set_input_path); + ClassDB::bind_method(D_METHOD("get_input_path"), &OpenXRDpadBindingModifier::get_input_path); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "input_path"), "set_input_path", "get_input_path"); + + ClassDB::bind_method(D_METHOD("set_threshold", "threshold"), &OpenXRDpadBindingModifier::set_threshold); + ClassDB::bind_method(D_METHOD("get_threshold"), &OpenXRDpadBindingModifier::get_threshold); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "threshold", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_threshold", "get_threshold"); + + ClassDB::bind_method(D_METHOD("set_threshold_released", "threshold_released"), &OpenXRDpadBindingModifier::set_threshold_released); + ClassDB::bind_method(D_METHOD("get_threshold_released"), &OpenXRDpadBindingModifier::get_threshold_released); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "threshold_released", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_threshold_released", "get_threshold_released"); + + ClassDB::bind_method(D_METHOD("set_center_region", "center_region"), &OpenXRDpadBindingModifier::set_center_region); + ClassDB::bind_method(D_METHOD("get_center_region"), &OpenXRDpadBindingModifier::get_center_region); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "center_region", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_center_region", "get_center_region"); + + ClassDB::bind_method(D_METHOD("set_wedge_angle", "wedge_angle"), &OpenXRDpadBindingModifier::set_wedge_angle); + ClassDB::bind_method(D_METHOD("get_wedge_angle"), &OpenXRDpadBindingModifier::get_wedge_angle); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wedge_angle"), "set_wedge_angle", "get_wedge_angle"); + + ClassDB::bind_method(D_METHOD("set_is_sticky", "is_sticky"), &OpenXRDpadBindingModifier::set_is_sticky); + ClassDB::bind_method(D_METHOD("get_is_sticky"), &OpenXRDpadBindingModifier::get_is_sticky); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "is_sticky"), "set_is_sticky", "get_is_sticky"); +} + +OpenXRDpadBindingModifier::OpenXRDpadBindingModifier() { + dpad_bindings.type = XR_TYPE_INTERACTION_PROFILE_DPAD_BINDING_EXT; + dpad_bindings.next = nullptr; + + dpad_bindings.forceThreshold = 0.6; + dpad_bindings.forceThresholdReleased = 0.4; + dpad_bindings.centerRegion = 0.1; + dpad_bindings.wedgeAngle = Math::deg_to_rad(90.0); + dpad_bindings.isSticky = false; +} + +void OpenXRDpadBindingModifier::set_action_set(const Ref p_action_set) { + action_set = p_action_set; +} + +Ref OpenXRDpadBindingModifier::get_action_set() const { + return action_set; +} + +void OpenXRDpadBindingModifier::set_input_path(const String &p_input_path) { + input_path = p_input_path; + emit_changed(); +} + +String OpenXRDpadBindingModifier::get_input_path() const { + return input_path; +} + +void OpenXRDpadBindingModifier::set_threshold(float p_threshold) { + ERR_FAIL_COND(p_threshold < 0.0 || p_threshold > 1.0); + + dpad_bindings.forceThreshold = p_threshold; + emit_changed(); +} + +float OpenXRDpadBindingModifier::get_threshold() const { + return dpad_bindings.forceThresholdReleased; +} + +void OpenXRDpadBindingModifier::set_threshold_released(float p_threshold) { + ERR_FAIL_COND(p_threshold < 0.0 || p_threshold > 1.0); + + dpad_bindings.forceThresholdReleased = p_threshold; + emit_changed(); +} + +float OpenXRDpadBindingModifier::get_threshold_released() const { + return dpad_bindings.forceThresholdReleased; +} + +void OpenXRDpadBindingModifier::set_center_region(float p_center_region) { + ERR_FAIL_COND(p_center_region < 0.0 || p_center_region > 1.0); + + dpad_bindings.centerRegion = p_center_region; + emit_changed(); +} + +float OpenXRDpadBindingModifier::get_center_region() const { + return dpad_bindings.centerRegion; +} + +void OpenXRDpadBindingModifier::set_wedge_angle(float p_wedge_angle) { + dpad_bindings.wedgeAngle = p_wedge_angle; + emit_changed(); +} + +float OpenXRDpadBindingModifier::get_wedge_angle() const { + return dpad_bindings.wedgeAngle; +} + +void OpenXRDpadBindingModifier::set_wedge_angle_deg(float p_wedge_angle) { + dpad_bindings.wedgeAngle = Math::deg_to_rad(p_wedge_angle); + emit_changed(); +} + +float OpenXRDpadBindingModifier::get_wedge_angle_deg() const { + return Math::rad_to_deg(dpad_bindings.wedgeAngle); +} + +void OpenXRDpadBindingModifier::set_is_sticky(bool p_sticky) { + dpad_bindings.isSticky = p_sticky; + emit_changed(); +} + +bool OpenXRDpadBindingModifier::get_is_sticky() const { + return dpad_bindings.isSticky; +} + +int OpenXRDpadBindingModifier::get_binding_modification_struct_size() const { + return sizeof(XrInteractionProfileDpadBindingEXT); +} + +const XrBindingModificationBaseHeaderKHR *OpenXRDpadBindingModifier::get_binding_modification() { + OpenXRDPadBindingExtension *dpad_binding_ext = OpenXRDPadBindingExtension::get_singleton(); + if (!dpad_binding_ext || !dpad_binding_ext->is_available()) { + // Extension not enabled! + WARN_PRINT("DPad binding extension is not enabled or available."); + return nullptr; + } + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, nullptr); + + dpad_bindings.binding = openxr_api->get_xr_path(input_path); + ERR_FAIL_COND_V(dpad_bindings.binding == XR_NULL_PATH, nullptr); + + // Get our action set + ERR_FAIL_COND_V(!action_set.is_valid(), nullptr); + RID action_set_rid = openxr_api->find_action_set(action_set->get_name()); + ERR_FAIL_COND_V(!action_set_rid.is_valid(), nullptr); + dpad_bindings.actionSet = openxr_api->action_set_get_handle(action_set_rid); + + // These are set already: + // - forceThreshold + // - forceThresholdReleased + // - centerRegion + // - wedgeAngle + // - isSticky + + // Not yet supported + dpad_bindings.onHaptic = nullptr; + dpad_bindings.offHaptic = nullptr; + + return (XrBindingModificationBaseHeaderKHR *)&dpad_bindings; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// OpenXRDpadBindingEditor + +#ifdef TOOLS_ENABLED + +void OpenXRDpadBindingEditor::_bind_methods() { +} + +OpenXRDpadBindingEditor::OpenXRDpadBindingEditor() { + threshold_property = memnew(EditorPropertyFloat); + threshold_property->setup(0.0, 1.0, 0.01, false, false, false, false, "", false); + add_property_editor("threshold", threshold_property); + + threshold_release_property = memnew(EditorPropertyFloat); + threshold_release_property->setup(0.0, 1.0, 0.01, false, false, false, false, "", false); + add_property_editor("threshold_release", threshold_release_property); + + center_region_property = memnew(EditorPropertyFloat); + center_region_property->setup(0.0, 1.0, 0.01, false, false, false, false, "", false); + add_property_editor("center_region", center_region_property); + + wedge_angle_property = memnew(EditorPropertyFloat); + wedge_angle_property->setup(0.0, 360.0, 1.0, false, false, false, false, "", true); + add_property_editor("wedge_angle", wedge_angle_property); + + is_sticky_property = memnew(EditorPropertyCheck); + add_property_editor("is_sticky", is_sticky_property); +} + +void OpenXRDpadBindingEditor::set_binding_modifier(Ref p_action_map, Ref p_binding_modifier) { + OpenXRBindingModifierEditor::set_binding_modifier(p_action_map, p_binding_modifier); +} + +#endif // TOOLS_ENABLED \ No newline at end of file diff --git a/modules/openxr/extensions/openxr_dpad_binding_extension.h b/modules/openxr/extensions/openxr_dpad_binding_extension.h new file mode 100644 index 000000000000..a1bf5805fd1c --- /dev/null +++ b/modules/openxr/extensions/openxr_dpad_binding_extension.h @@ -0,0 +1,128 @@ +/**************************************************************************/ +/* openxr_dpad_binding_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_DPAD_BINDING_EXTENSION_H +#define OPENXR_DPAD_BINDING_EXTENSION_H + +#include "../action_map/openxr_action_set.h" +#include "../action_map/openxr_binding_modifier.h" +#include "../util.h" +#include "openxr_extension_wrapper.h" + +#ifdef TOOLS_ENABLED +#include "../editor/openxr_binding_modifier_editor.h" +#endif // TOOLS_ENABLED + +class OpenXRDPadBindingExtension : public OpenXRExtensionWrapper { +public: + static OpenXRDPadBindingExtension *get_singleton(); + + OpenXRDPadBindingExtension(); + virtual ~OpenXRDPadBindingExtension() override; + + virtual HashMap get_requested_extensions() override; + + bool is_available(); + +private: + static OpenXRDPadBindingExtension *singleton; + + bool binding_modifier_ext = false; + bool dpad_binding_ext = false; +}; + +class OpenXRDpadBindingModifier : public OpenXRBindingModifier { + GDCLASS(OpenXRDpadBindingModifier, OpenXRBindingModifier); + +private: + XrInteractionProfileDpadBindingEXT dpad_bindings; + String input_path; + Ref action_set; + +protected: + static void _bind_methods(); + +public: + OpenXRDpadBindingModifier(); + + void set_action_set(const Ref p_action_set); + Ref get_action_set() const; + + void set_input_path(const String &p_input_path); + String get_input_path() const; + + void set_threshold(float p_threshold); + float get_threshold() const; + + void set_threshold_released(float p_threshold); + float get_threshold_released() const; + + void set_center_region(float p_center_region); + float get_center_region() const; + + void set_wedge_angle(float p_wedge_angle); + float get_wedge_angle() const; + + void set_wedge_angle_deg(float p_wedge_angle); + float get_wedge_angle_deg() const; + + void set_is_sticky(bool p_sticky); + bool get_is_sticky() const; + + virtual bool record_on_binding() const override { return false; } + virtual String get_description() const override { return "DPad modifier"; } + virtual int get_binding_modification_struct_size() const override; + virtual const XrBindingModificationBaseHeaderKHR *get_binding_modification() override; +}; + +#ifdef TOOLS_ENABLED + +class OpenXRDpadBindingEditor : public OpenXRBindingModifierEditor { + GDCLASS(OpenXRDpadBindingEditor, OpenXRBindingModifierEditor); + +private: + EditorPropertyFloat *threshold_property = nullptr; + EditorPropertyFloat *threshold_release_property = nullptr; + EditorPropertyFloat *center_region_property = nullptr; + EditorPropertyFloat *wedge_angle_property = nullptr; + EditorPropertyCheck *is_sticky_property = nullptr; + +protected: + static void _bind_methods(); + +public: + virtual void set_binding_modifier(Ref p_action_map, Ref p_binding_modifier) override; + + OpenXRDpadBindingEditor(); +}; + +#endif // TOOLS_ENABLED + +#endif // OPENXR_DPAD_BINDING_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_valve_analog_threshold_extension.cpp b/modules/openxr/extensions/openxr_valve_analog_threshold_extension.cpp new file mode 100644 index 000000000000..d6f5f21bd618 --- /dev/null +++ b/modules/openxr/extensions/openxr_valve_analog_threshold_extension.cpp @@ -0,0 +1,203 @@ +/**************************************************************************/ +/* openxr_valve_analog_threshold_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_valve_analog_threshold_extension.h" +#include "../action_map/openxr_action_set.h" +#include "../openxr_api.h" + +// Implementation for: +// https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_VALVE_analog_threshold + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// OpenXRValveAnalogThresholdExtension + +OpenXRValveAnalogThresholdExtension *OpenXRValveAnalogThresholdExtension::singleton = nullptr; + +OpenXRValveAnalogThresholdExtension *OpenXRValveAnalogThresholdExtension::get_singleton() { + return singleton; +} + +OpenXRValveAnalogThresholdExtension::OpenXRValveAnalogThresholdExtension() { + singleton = this; +} + +OpenXRValveAnalogThresholdExtension::~OpenXRValveAnalogThresholdExtension() { + singleton = nullptr; +} + +HashMap OpenXRValveAnalogThresholdExtension::get_requested_extensions() { + HashMap request_extensions; + + // Note, we're dependent on the binding modifier extension, this may be requested by multiple extension wrappers. + request_extensions[XR_KHR_BINDING_MODIFICATION_EXTENSION_NAME] = &binding_modifier_ext; + request_extensions[XR_VALVE_ANALOG_THRESHOLD_EXTENSION_NAME] = &threshold_ext; + + return request_extensions; +} + +bool OpenXRValveAnalogThresholdExtension::is_available() { + return binding_modifier_ext && threshold_ext; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// OpenXRAnalogThresholdModifier + +void OpenXRAnalogThresholdModifier::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_action", "action"), &OpenXRAnalogThresholdModifier::set_action); + ClassDB::bind_method(D_METHOD("get_action"), &OpenXRAnalogThresholdModifier::get_action); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "action", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRAction"), "set_action", "get_action"); + + ClassDB::bind_method(D_METHOD("set_input_path", "input_path"), &OpenXRAnalogThresholdModifier::set_input_path); + ClassDB::bind_method(D_METHOD("get_input_path"), &OpenXRAnalogThresholdModifier::get_input_path); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "input_path"), "set_input_path", "get_input_path"); + + ClassDB::bind_method(D_METHOD("set_on_threshold", "on_threshold"), &OpenXRAnalogThresholdModifier::set_on_threshold); + ClassDB::bind_method(D_METHOD("get_on_threshold"), &OpenXRAnalogThresholdModifier::get_on_threshold); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "on_threshold", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_on_threshold", "get_on_threshold"); + + ClassDB::bind_method(D_METHOD("set_off_threshold", "off_threshold"), &OpenXRAnalogThresholdModifier::set_off_threshold); + ClassDB::bind_method(D_METHOD("get_off_threshold"), &OpenXRAnalogThresholdModifier::get_off_threshold); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "off_threshold", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_off_threshold", "get_off_threshold"); +} + +OpenXRAnalogThresholdModifier::OpenXRAnalogThresholdModifier() { + analog_threshold.type = XR_TYPE_INTERACTION_PROFILE_ANALOG_THRESHOLD_VALVE; + analog_threshold.next = nullptr; + + analog_threshold.onThreshold = 0.6; + analog_threshold.offThreshold = 0.4; +} + +void OpenXRAnalogThresholdModifier::set_action(const Ref p_action) { + action = p_action; + emit_changed(); +} + +Ref OpenXRAnalogThresholdModifier::get_action() const { + return action; +} + +void OpenXRAnalogThresholdModifier::set_input_path(const String &p_input_path) { + input_path = p_input_path; + emit_changed(); +} + +String OpenXRAnalogThresholdModifier::get_input_path() const { + return input_path; +} + +void OpenXRAnalogThresholdModifier::set_on_threshold(float p_threshold) { + ERR_FAIL_COND(p_threshold < 0.0 || p_threshold > 1.0); + + analog_threshold.onThreshold = p_threshold; + emit_changed(); +} + +float OpenXRAnalogThresholdModifier::get_on_threshold() const { + return analog_threshold.onThreshold; +} + +void OpenXRAnalogThresholdModifier::set_off_threshold(float p_threshold) { + ERR_FAIL_COND(p_threshold < 0.0 || p_threshold > 1.0); + + analog_threshold.offThreshold = p_threshold; + emit_changed(); +} + +float OpenXRAnalogThresholdModifier::get_off_threshold() const { + return analog_threshold.offThreshold; +} + +int OpenXRAnalogThresholdModifier::get_binding_modification_struct_size() const { + return sizeof(XrInteractionProfileAnalogThresholdVALVE); +} + +const XrBindingModificationBaseHeaderKHR *OpenXRAnalogThresholdModifier::get_binding_modification() { + OpenXRValveAnalogThresholdExtension *analog_threshold_ext = OpenXRValveAnalogThresholdExtension::get_singleton(); + if (!analog_threshold_ext || !analog_threshold_ext->is_available()) { + // Extension not enabled! + WARN_PRINT("Analog threshold extension is not enabled or available."); + return nullptr; + } + + ERR_FAIL_COND_V(!action.is_valid(), nullptr); + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, nullptr); + + // Get our action set + Ref action_set = action->get_action_set(); + ERR_FAIL_COND_V(!action_set.is_valid(), nullptr); + RID action_set_rid = openxr_api->find_action_set(action_set->get_name()); + ERR_FAIL_COND_V(!action_set_rid.is_valid(), nullptr); + + // Get our action + RID action_rid = openxr_api->find_action(action->get_name(), action_set_rid); + ERR_FAIL_COND_V(!action_rid.is_valid(), nullptr); + + analog_threshold.action = openxr_api->action_get_handle(action_rid); + + analog_threshold.binding = openxr_api->get_xr_path(input_path); + ERR_FAIL_COND_V(analog_threshold.binding == XR_NULL_PATH, nullptr); + + // These are set already: + // - analog_threshold.onThreshold + // - analog_threshold.offThreshold + + // Not yet supported + analog_threshold.onHaptic = nullptr; + analog_threshold.offHaptic = nullptr; + + return (XrBindingModificationBaseHeaderKHR *)&analog_threshold; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// OpenXRAnalogThresholdEditor + +#ifdef TOOLS_ENABLED + +void OpenXRAnalogThresholdEditor::_bind_methods() { +} + +OpenXRAnalogThresholdEditor::OpenXRAnalogThresholdEditor() { + on_threshold_property = memnew(EditorPropertyFloat); + on_threshold_property->setup(0.0, 1.0, 0.01, false, false, false, false, "", false); + add_property_editor("on_property", on_threshold_property); + + off_threshold_property = memnew(EditorPropertyFloat); + off_threshold_property->setup(0.0, 1.0, 0.01, false, false, false, false, "", false); + add_property_editor("off_property", off_threshold_property); +} + +void OpenXRAnalogThresholdEditor::set_binding_modifier(Ref p_action_map, Ref p_binding_modifier) { + OpenXRBindingModifierEditor::set_binding_modifier(p_action_map, p_binding_modifier); +} + +#endif // TOOLS_ENABLED diff --git a/modules/openxr/extensions/openxr_valve_analog_threshold_extension.h b/modules/openxr/extensions/openxr_valve_analog_threshold_extension.h new file mode 100644 index 000000000000..0a831ca7c5cc --- /dev/null +++ b/modules/openxr/extensions/openxr_valve_analog_threshold_extension.h @@ -0,0 +1,113 @@ +/**************************************************************************/ +/* openxr_valve_analog_threshold_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_VALVE_ANALOG_THRESHOLD_EXTENSION_H +#define OPENXR_VALVE_ANALOG_THRESHOLD_EXTENSION_H + +#include "../action_map/openxr_binding_modifier.h" +#include "../util.h" +#include "core/io/resource.h" +#include "openxr_extension_wrapper.h" + +#ifdef TOOLS_ENABLED +#include "../editor/openxr_binding_modifier_editor.h" +#endif // TOOLS_ENABLED + +class OpenXRValveAnalogThresholdExtension : public OpenXRExtensionWrapper { +public: + static OpenXRValveAnalogThresholdExtension *get_singleton(); + + OpenXRValveAnalogThresholdExtension(); + virtual ~OpenXRValveAnalogThresholdExtension() override; + + virtual HashMap get_requested_extensions() override; + + bool is_available(); + +private: + static OpenXRValveAnalogThresholdExtension *singleton; + + bool binding_modifier_ext = false; + bool threshold_ext = false; +}; + +class OpenXRAnalogThresholdModifier : public OpenXRBindingModifier { + GDCLASS(OpenXRAnalogThresholdModifier, OpenXRBindingModifier); + +private: + XrInteractionProfileAnalogThresholdVALVE analog_threshold; + + Ref action; + String input_path; + +protected: + static void _bind_methods(); + +public: + OpenXRAnalogThresholdModifier(); + + void set_action(const Ref p_action); + Ref get_action() const; + + void set_input_path(const String &p_input_path); + String get_input_path() const; + + void set_on_threshold(float p_threshold); + float get_on_threshold() const; + + void set_off_threshold(float p_threshold); + float get_off_threshold() const; + + virtual String get_description() const override { return "Analog threshold modifier"; } + virtual int get_binding_modification_struct_size() const override; + virtual const XrBindingModificationBaseHeaderKHR *get_binding_modification() override; +}; + +#ifdef TOOLS_ENABLED + +class OpenXRAnalogThresholdEditor : public OpenXRBindingModifierEditor { + GDCLASS(OpenXRAnalogThresholdEditor, OpenXRBindingModifierEditor); + +private: + EditorPropertyFloat *on_threshold_property = nullptr; + EditorPropertyFloat *off_threshold_property = nullptr; + +protected: + static void _bind_methods(); + +public: + virtual void set_binding_modifier(Ref p_action_map, Ref p_binding_modifier) override; + + OpenXRAnalogThresholdEditor(); +}; + +#endif // TOOLS_ENABLED + +#endif // OPENXR_VALVE_ANALOG_THRESHOLD_EXTENSION_H diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index c67be5a2b313..8fb2ac942dec 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -542,11 +542,23 @@ bool OpenXRAPI::create_instance() { // Set this extension as supported. *requested_extension.value = true; - // And record that we want to enable it. - enabled_extensions.push_back(requested_extension.key.ascii()); + // And record that we want to enable it (dependent extensions may be requested multiple times). + CharString ext_name = requested_extension.key.ascii(); + if (!enabled_extensions.has(ext_name)) { + enabled_extensions.push_back(ext_name); + } else { + // just testing + print_line("Extension " + requested_extension.key + " was already requested."); + } } else { - // Record that we want to enable this. - enabled_extensions.push_back(requested_extension.key.ascii()); + // Record that we want to enable this (dependent extensions may be requested multiple times). + CharString ext_name = requested_extension.key.ascii(); + if (!enabled_extensions.has(ext_name)) { + enabled_extensions.push_back(ext_name); + } else { + // just testing + print_line("Extension " + requested_extension.key + " was already requested."); + } } } @@ -2763,6 +2775,25 @@ bool OpenXRAPI::xr_result(XrResult result, const char *format, Array args) const return false; } +XrPath OpenXRAPI::get_xr_path(const String &p_path) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, XR_NULL_PATH); + + if (p_path.is_empty()) { + // This isn't necesairily an issue, so silently return a null path. + return XR_NULL_PATH; + } + + XrPath path = XR_NULL_PATH; + + XrResult result = xrStringToPath(instance, p_path.utf8().get_data(), &path); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to get path for ", p_path, "! [", get_error_string(result), "]"); + return XR_NULL_PATH; + } + + return path; +} + RID OpenXRAPI::get_tracker_rid(XrPath p_path) { List current; tracker_owner.get_owned_list(¤t); @@ -2797,11 +2828,8 @@ RID OpenXRAPI::tracker_create(const String p_name) { new_tracker.toplevel_path = XR_NULL_PATH; new_tracker.active_profile_rid = RID(); - XrResult result = xrStringToPath(instance, p_name.utf8().get_data(), &new_tracker.toplevel_path); - if (XR_FAILED(result)) { - print_line("OpenXR: failed to get path for ", p_name, "! [", get_error_string(result), "]"); - return RID(); - } + new_tracker.toplevel_path = get_xr_path(p_name); + ERR_FAIL_COND_V(new_tracker.toplevel_path == XR_NULL_PATH, RID()); return tracker_owner.make_rid(new_tracker); } @@ -2892,6 +2920,19 @@ RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_n return action_set_owner.make_rid(action_set); } +RID OpenXRAPI::find_action_set(const String p_name) { + List current; + action_set_owner.get_owned_list(¤t); + for (const RID &E : current) { + ActionSet *action_set = action_set_owner.get_or_null(E); + if (action_set && action_set->name == p_name) { + return E; + } + } + + return RID(); +} + String OpenXRAPI::action_set_get_name(RID p_action_set) { if (p_action_set.is_null()) { return String("None"); @@ -2903,6 +2944,17 @@ String OpenXRAPI::action_set_get_name(RID p_action_set) { return action_set->name; } +XrActionSet OpenXRAPI::action_set_get_handle(RID p_action_set) { + if (p_action_set.is_null()) { + return XR_NULL_HANDLE; + } + + ActionSet *action_set = action_set_owner.get_or_null(p_action_set); + ERR_FAIL_NULL_V(action_set, XR_NULL_HANDLE); + + return action_set->handle; +} + bool OpenXRAPI::attach_action_sets(const Vector &p_action_sets) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); @@ -2985,12 +3037,12 @@ RID OpenXRAPI::get_action_rid(XrAction p_action) { return RID(); } -RID OpenXRAPI::find_action(const String &p_name) { +RID OpenXRAPI::find_action(const String &p_name, const RID &p_action_set) { List current; action_owner.get_owned_list(¤t); for (const RID &E : current) { Action *action = action_owner.get_or_null(E); - if (action && action->name == p_name) { + if (action && action->name == p_name && (p_action_set.is_null() || action->action_set_rid == p_action_set)) { return E; } } @@ -3080,6 +3132,17 @@ String OpenXRAPI::action_get_name(RID p_action) { return action->name; } +XrAction OpenXRAPI::action_get_handle(RID p_action) { + if (p_action.is_null()) { + return XR_NULL_HANDLE; + } + + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, XR_NULL_HANDLE); + + return action->handle; +} + void OpenXRAPI::action_free(RID p_action) { Action *action = action_owner.get_or_null(p_action); ERR_FAIL_NULL(action); @@ -3182,15 +3245,43 @@ bool OpenXRAPI::interaction_profile_add_binding(RID p_interaction_profile, RID p return true; } +bool OpenXRAPI::interaction_profile_add_modifier(RID p_interaction_profile, const XrBindingModificationBaseHeaderKHR *p_modifier, uint32_t p_size) { + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL_V(ip, false); + + if (p_modifier && p_size > 0) { + // We make a copy of the data we get. + XrBindingModificationBaseHeaderKHR *modifier = (XrBindingModificationBaseHeaderKHR *)memalloc(p_size); + memcpy(modifier, p_modifier, p_size); + + // And add it to our stack. + ip->modifiers.push_back(modifier); + } + + return true; +} + bool OpenXRAPI::interaction_profile_suggest_bindings(RID p_interaction_profile) { ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); ERR_FAIL_NULL_V(ip, false); + void *next = nullptr; + + // Note, extensions should only add binding modifiers if they are supported, else this may fail. + XrBindingModificationsKHR binding_modifiers; + if (!ip->modifiers.is_empty()) { + binding_modifiers.type = XR_TYPE_BINDING_MODIFICATIONS_KHR; + binding_modifiers.next = next; + binding_modifiers.bindingModificationCount = ip->modifiers.size(); + binding_modifiers.bindingModifications = ip->modifiers.ptr(); + next = &binding_modifiers; + } + const XrInteractionProfileSuggestedBinding suggested_bindings = { XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING, // type - nullptr, // next + next, // next ip->path, // interactionProfile uint32_t(ip->bindings.size()), // countSuggestedBindings ip->bindings.ptr() // suggestedBindings @@ -3230,6 +3321,11 @@ void OpenXRAPI::interaction_profile_free(RID p_interaction_profile) { ip->bindings.clear(); + for (XrBindingModificationBaseHeaderKHR *modifier : ip->modifiers) { + memfree(modifier); + } + ip->modifiers.clear(); + interaction_profile_owner.free(p_interaction_profile); } diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 0d1e4eb414ac..0d2c9e328b1e 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -300,6 +300,7 @@ class OpenXRAPI { String name; // Name of the interaction profile (i.e. "/interaction_profiles/valve/index_controller") XrPath path; // OpenXR path for this profile Vector bindings; // OpenXR action bindings + Vector modifiers; // OpenXR binding modifiers }; RID_Owner interaction_profile_owner; RID get_interaction_profile_rid(XrPath p_path); @@ -410,8 +411,8 @@ class OpenXRAPI { XRPose::TrackingConfidence transform_from_location(const XrSpaceLocation &p_location, Transform3D &r_transform); XRPose::TrackingConfidence transform_from_location(const XrHandJointLocationEXT &p_location, Transform3D &r_transform); void parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); - bool xr_result(XrResult result, const char *format, Array args = Array()) const; + XrPath get_xr_path(const String &p_path); bool is_top_level_path_supported(const String &p_toplevel_path); bool is_interaction_profile_supported(const String &p_ip_path); bool interaction_profile_supports_io_path(const String &p_ip_path, const String &p_io_path); @@ -515,22 +516,26 @@ class OpenXRAPI { RID action_set_create(const String p_name, const String p_localized_name, const int p_priority); String action_set_get_name(RID p_action_set); + XrActionSet action_set_get_handle(RID p_action_set); bool attach_action_sets(const Vector &p_action_sets); void action_set_free(RID p_action_set); RID action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector &p_trackers); String action_get_name(RID p_action); + XrAction action_get_handle(RID p_action); void action_free(RID p_action); RID interaction_profile_create(const String p_name); String interaction_profile_get_name(RID p_interaction_profile); void interaction_profile_clear_bindings(RID p_interaction_profile); bool interaction_profile_add_binding(RID p_interaction_profile, RID p_action, const String p_path); + bool interaction_profile_add_modifier(RID p_interaction_profile, const XrBindingModificationBaseHeaderKHR *p_modifier, uint32_t p_size); bool interaction_profile_suggest_bindings(RID p_interaction_profile); void interaction_profile_free(RID p_interaction_profile); RID find_tracker(const String &p_name); - RID find_action(const String &p_name); + RID find_action_set(const String p_name); + RID find_action(const String &p_name, const RID &p_action_set = RID()); bool sync_action_sets(const Vector p_active_sets); bool get_action_bool(RID p_action, RID p_tracker); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index a8e62ba6bf00..f452bb6e3af2 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -287,6 +287,16 @@ void OpenXRInterface::_load_action_map() { if (ip.is_valid()) { openxr_api->interaction_profile_clear_bindings(ip); + Array xr_binding_modifiers = xr_interaction_profile->get_binding_modifiers(); + for (int j = 0; j < xr_binding_modifiers.size(); j++) { + Ref xr_binding_modifier = xr_binding_modifiers[j]; + + const XrBindingModificationBaseHeaderKHR *bm = xr_binding_modifier->get_binding_modification(); + if (bm != nullptr) { + openxr_api->interaction_profile_add_modifier(ip, bm, xr_binding_modifier->get_binding_modification_struct_size()); + } + } + Array xr_bindings = xr_interaction_profile->get_bindings(); for (int j = 0; j < xr_bindings.size(); j++) { Ref xr_binding = xr_bindings[j]; diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index f3fda2517ce6..4f0177ad7e18 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -49,6 +49,7 @@ #include "extensions/openxr_composition_layer_depth_extension.h" #include "extensions/openxr_composition_layer_extension.h" #include "extensions/openxr_debug_utils_extension.h" +#include "extensions/openxr_dpad_binding_extension.h" #include "extensions/openxr_eye_gaze_interaction.h" #include "extensions/openxr_fb_display_refresh_rate_extension.h" #include "extensions/openxr_hand_interaction_extension.h" @@ -62,6 +63,7 @@ #include "extensions/openxr_mxink_extension.h" #include "extensions/openxr_palm_pose_extension.h" #include "extensions/openxr_pico_controller_extension.h" +#include "extensions/openxr_valve_analog_threshold_extension.h" #include "extensions/openxr_visibility_mask_extension.h" #include "extensions/openxr_wmr_controller_extension.h" @@ -78,6 +80,10 @@ #ifdef TOOLS_ENABLED #include "editor/editor_node.h" + +#include "editor/openxr_binding_modifier_editor.h" +#include "editor/openxr_interaction_profile_editor.h" + #endif static OpenXRAPI *openxr_api = nullptr; @@ -140,6 +146,14 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { if (GLOBAL_GET("xr/openxr/extensions/hand_tracking")) { OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandTrackingExtension)); } + + // register gated binding modifiers + if (GLOBAL_GET("xr/openxr/binding_modifiers/analog_threshold")) { + OpenXRAPI::register_extension_wrapper(memnew(OpenXRValveAnalogThresholdExtension)); + } + if (GLOBAL_GET("xr/openxr/binding_modifiers/dpad_binding")) { + OpenXRAPI::register_extension_wrapper(memnew(OpenXRDPadBindingExtension)); + } } if (OpenXRAPI::openxr_is_enabled()) { @@ -181,6 +195,10 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(OpenXRIPBinding); GDREGISTER_CLASS(OpenXRInteractionProfile); + GDREGISTER_ABSTRACT_CLASS(OpenXRBindingModifier); + GDREGISTER_CLASS(OpenXRAnalogThresholdModifier); + GDREGISTER_CLASS(OpenXRDpadBindingModifier); + GDREGISTER_ABSTRACT_CLASS(OpenXRCompositionLayer); GDREGISTER_CLASS(OpenXRCompositionLayerEquirect); GDREGISTER_CLASS(OpenXRCompositionLayerCylinder); @@ -201,6 +219,12 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { } #ifdef TOOLS_ENABLED + GDREGISTER_ABSTRACT_CLASS(OpenXRInteractionProfileEditorBase); + GDREGISTER_CLASS(OpenXRInteractionProfileEditor); + GDREGISTER_ABSTRACT_CLASS(OpenXRBindingModifierEditor); + GDREGISTER_CLASS(OpenXRAnalogThresholdEditor); + GDREGISTER_CLASS(OpenXRDpadBindingEditor); + EditorNode::add_init_callback(_editor_init); #endif }