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..88dd298fd29b
--- /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
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
}
]