diff options
Diffstat (limited to 'modules/openxr')
46 files changed, 4258 insertions, 443 deletions
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index 37a8f3909a..593d1ff3c1 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -35,7 +35,11 @@ if env["platform"] == "android": # may need to include java parts of the openxr loader elif env["platform"] == "linuxbsd": - env_thirdparty.AppendUnique(CPPDEFINES=["XR_OS_LINUX", "XR_USE_PLATFORM_XLIB"]) + env_thirdparty.AppendUnique(CPPDEFINES=["XR_OS_LINUX"]) + + if env["x11"]: + env_thirdparty.AppendUnique(CPPDEFINES=["XR_USE_PLATFORM_XLIB"]) + # FIXME: Review what needs to be set for Android and macOS. env_thirdparty.AppendUnique(CPPDEFINES=["HAVE_SECURE_GETENV"]) elif env["platform"] == "windows": @@ -75,13 +79,18 @@ module_obj = [] env_openxr.add_source_files(module_obj, "*.cpp") env_openxr.add_source_files(module_obj, "action_map/*.cpp") -# We're a little more targetted with our extensions +# We're a little more targeted with our extensions if env["platform"] == "android": env_openxr.add_source_files(module_obj, "extensions/openxr_android_extension.cpp") if env["vulkan"]: env_openxr.add_source_files(module_obj, "extensions/openxr_vulkan_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_htc_vive_tracker_extension.cpp") + env.modules_sources += module_obj +if env["tools"]: + SConscript("editor/SCsub") + # Needed to force rebuilding the module files when the thirdparty library is updated. env.Depends(module_obj, thirdparty_obj) diff --git a/modules/openxr/action_map/openxr_action.cpp b/modules/openxr/action_map/openxr_action.cpp index 59ee3f4292..359975a480 100644 --- a/modules/openxr/action_map/openxr_action.cpp +++ b/modules/openxr/action_map/openxr_action.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "openxr_action.h" +#include "openxr_action_set.h" void OpenXRAction::_bind_methods() { ClassDB::bind_method(D_METHOD("set_localized_name", "localized_name"), &OpenXRAction::set_localized_name); @@ -62,6 +63,16 @@ Ref<OpenXRAction> OpenXRAction::new_action(const char *p_name, const char *p_loc return action; } +String OpenXRAction::get_name_with_set() const { + String name = get_name(); + + if (action_set != nullptr) { + name = action_set->get_name() + "/" + name; + } + + return name; +} + void OpenXRAction::set_localized_name(const String p_localized_name) { localized_name = p_localized_name; } @@ -86,6 +97,18 @@ PackedStringArray OpenXRAction::get_toplevel_paths() const { return toplevel_paths; } +void OpenXRAction::add_toplevel_path(const String p_toplevel_path) { + if (!toplevel_paths.has(p_toplevel_path)) { + toplevel_paths.push_back(p_toplevel_path); + } +} + +void OpenXRAction::rem_toplevel_path(const String p_toplevel_path) { + if (toplevel_paths.has(p_toplevel_path)) { + toplevel_paths.erase(p_toplevel_path); + } +} + void OpenXRAction::parse_toplevel_paths(const String p_toplevel_paths) { toplevel_paths = p_toplevel_paths.split(",", false); } diff --git a/modules/openxr/action_map/openxr_action.h b/modules/openxr/action_map/openxr_action.h index e2cfe79e64..a7c1c9988c 100644 --- a/modules/openxr/action_map/openxr_action.h +++ b/modules/openxr/action_map/openxr_action.h @@ -33,6 +33,8 @@ #include "core/io/resource.h" +class OpenXRActionSet; + class OpenXRAction : public Resource { GDCLASS(OpenXRAction, Resource); @@ -43,6 +45,7 @@ public: OPENXR_ACTION_VECTOR2, OPENXR_ACTION_POSE, OPENXR_ACTION_HAPTIC, + OPENXR_ACTION_MAX }; private: @@ -52,23 +55,33 @@ private: PackedStringArray toplevel_paths; protected: + friend class OpenXRActionSet; + + OpenXRActionSet *action_set = nullptr; // action belongs to this action set. + static void _bind_methods(); public: - static Ref<OpenXRAction> new_action(const char *p_name, const char *p_localized_name, const ActionType p_action_type, const char *p_toplevel_paths); + static Ref<OpenXRAction> new_action(const char *p_name, const char *p_localized_name, const ActionType p_action_type, const char *p_toplevel_paths); // Helper function to add and configure an action + OpenXRActionSet *get_action_set() const { return action_set; } // Get the action set this action belongs to + + String get_name_with_set() const; // Retrieve the name of this action as <action_set>/<action> + + void set_localized_name(const String p_localized_name); // Set the localized name of this action + String get_localized_name() const; // Get the localized name of this action - void set_localized_name(const String p_localized_name); - String get_localized_name() const; + void set_action_type(const ActionType p_action_type); // Set the type of this action + ActionType get_action_type() const; // Get the type of this action - void set_action_type(const ActionType p_action_type); - ActionType get_action_type() const; + void set_toplevel_paths(const PackedStringArray p_toplevel_paths); // Set the toplevel paths of this action + PackedStringArray get_toplevel_paths() const; // Get the toplevel paths of this action - void set_toplevel_paths(const PackedStringArray p_toplevel_paths); - PackedStringArray get_toplevel_paths() const; + void add_toplevel_path(const String p_toplevel_path); // Add a top level path to this action + void rem_toplevel_path(const String p_toplevel_path); // Remove a toplevel path from this action - void parse_toplevel_paths(const String p_toplevel_paths); + void parse_toplevel_paths(const String p_toplevel_paths); // Parse and set the top level paths from a comma separated string }; VARIANT_ENUM_CAST(OpenXRAction::ActionType); -#endif // !OPENXR_ACTION_H +#endif // OPENXR_ACTION_H diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp index 5391f9569a..0eb5302442 100644 --- a/modules/openxr/action_map/openxr_action_map.cpp +++ b/modules/openxr/action_map/openxr_action_map.cpp @@ -35,6 +35,9 @@ void OpenXRActionMap::_bind_methods() { ClassDB::bind_method(D_METHOD("get_action_sets"), &OpenXRActionMap::get_action_sets); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "action_sets", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRActionSet", PROPERTY_USAGE_NO_EDITOR), "set_action_sets", "get_action_sets"); + ClassDB::bind_method(D_METHOD("get_action_set_count"), &OpenXRActionMap::get_action_set_count); + ClassDB::bind_method(D_METHOD("find_action_set", "name"), &OpenXRActionMap::find_action_set); + ClassDB::bind_method(D_METHOD("get_action_set", "idx"), &OpenXRActionMap::get_action_set); ClassDB::bind_method(D_METHOD("add_action_set", "action_set"), &OpenXRActionMap::add_action_set); ClassDB::bind_method(D_METHOD("remove_action_set", "action_set"), &OpenXRActionMap::remove_action_set); @@ -42,6 +45,9 @@ void OpenXRActionMap::_bind_methods() { ClassDB::bind_method(D_METHOD("get_interaction_profiles"), &OpenXRActionMap::get_interaction_profiles); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "interaction_profiles", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRInteractionProfile", PROPERTY_USAGE_NO_EDITOR), "set_interaction_profiles", "get_interaction_profiles"); + ClassDB::bind_method(D_METHOD("get_interaction_profile_count"), &OpenXRActionMap::get_interaction_profile_count); + ClassDB::bind_method(D_METHOD("find_interaction_profile", "name"), &OpenXRActionMap::find_interaction_profile); + ClassDB::bind_method(D_METHOD("get_interaction_profile", "idx"), &OpenXRActionMap::get_interaction_profile); ClassDB::bind_method(D_METHOD("add_interaction_profile", "interaction_profile"), &OpenXRActionMap::add_interaction_profile); ClassDB::bind_method(D_METHOD("remove_interaction_profile", "interaction_profile"), &OpenXRActionMap::remove_interaction_profile); @@ -49,13 +55,41 @@ void OpenXRActionMap::_bind_methods() { } void OpenXRActionMap::set_action_sets(Array p_action_sets) { - action_sets = p_action_sets; + action_sets.clear(); + + for (int i = 0; i < p_action_sets.size(); i++) { + Ref<OpenXRActionSet> action_set = p_action_sets[i]; + if (action_set.is_valid() && action_sets.find(action_set) == -1) { + action_sets.push_back(action_set); + } + } } Array OpenXRActionMap::get_action_sets() const { return action_sets; } +int OpenXRActionMap::get_action_set_count() const { + return action_sets.size(); +} + +Ref<OpenXRActionSet> OpenXRActionMap::find_action_set(String p_name) const { + for (int i = 0; i < action_sets.size(); i++) { + Ref<OpenXRActionSet> action_set = action_sets[i]; + if (action_set->get_name() == p_name) { + return action_set; + } + } + + return Ref<OpenXRActionSet>(); +} + +Ref<OpenXRActionSet> OpenXRActionMap::get_action_set(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, action_sets.size(), Ref<OpenXRActionSet>()); + + return action_sets[p_idx]; +} + void OpenXRActionMap::add_action_set(Ref<OpenXRActionSet> p_action_set) { ERR_FAIL_COND(p_action_set.is_null()); @@ -72,13 +106,41 @@ void OpenXRActionMap::remove_action_set(Ref<OpenXRActionSet> p_action_set) { } void OpenXRActionMap::set_interaction_profiles(Array p_interaction_profiles) { - interaction_profiles = p_interaction_profiles; + interaction_profiles.clear(); + + for (int i = 0; i < p_interaction_profiles.size(); i++) { + Ref<OpenXRInteractionProfile> interaction_profile = p_interaction_profiles[i]; + if (interaction_profile.is_valid() && interaction_profiles.find(interaction_profile) == -1) { + interaction_profiles.push_back(interaction_profile); + } + } } Array OpenXRActionMap::get_interaction_profiles() const { return interaction_profiles; } +int OpenXRActionMap::get_interaction_profile_count() const { + return interaction_profiles.size(); +} + +Ref<OpenXRInteractionProfile> OpenXRActionMap::find_interaction_profile(String p_path) const { + for (int i = 0; i < interaction_profiles.size(); i++) { + Ref<OpenXRInteractionProfile> interaction_profile = interaction_profiles[i]; + if (interaction_profile->get_interaction_profile_path() == p_path) { + return interaction_profile; + } + } + + return Ref<OpenXRInteractionProfile>(); +} + +Ref<OpenXRInteractionProfile> OpenXRActionMap::get_interaction_profile(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, interaction_profiles.size(), Ref<OpenXRInteractionProfile>()); + + return interaction_profiles[p_idx]; +} + void OpenXRActionMap::add_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile) { ERR_FAIL_COND(p_interaction_profile.is_null()); @@ -120,10 +182,40 @@ void OpenXRActionMap::create_default_action_sets() { Ref<OpenXRAction> ax_touch = action_set->add_new_action("ax_touch", "A/X touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> by_button = action_set->add_new_action("by_button", "B/Y button", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> by_touch = action_set->add_new_action("by_touch", "B/Y touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); - Ref<OpenXRAction> default_pose = action_set->add_new_action("default_pose", "Default pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> default_pose = action_set->add_new_action("default_pose", "Default pose", OpenXRAction::OPENXR_ACTION_POSE, + "/user/hand/left," + "/user/hand/right," + // "/user/vive_tracker_htcx/role/handheld_object," <-- getting errors on this one + "/user/vive_tracker_htcx/role/left_foot," + "/user/vive_tracker_htcx/role/right_foot," + "/user/vive_tracker_htcx/role/left_shoulder," + "/user/vive_tracker_htcx/role/right_shoulder," + "/user/vive_tracker_htcx/role/left_elbow," + "/user/vive_tracker_htcx/role/right_elbow," + "/user/vive_tracker_htcx/role/left_knee," + "/user/vive_tracker_htcx/role/right_knee," + "/user/vive_tracker_htcx/role/waist," + "/user/vive_tracker_htcx/role/chest," + "/user/vive_tracker_htcx/role/camera," + "/user/vive_tracker_htcx/role/keyboard"); Ref<OpenXRAction> aim_pose = action_set->add_new_action("aim_pose", "Aim pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> grip_pose = action_set->add_new_action("grip_pose", "Grip pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); - Ref<OpenXRAction> haptic = action_set->add_new_action("haptic", "Haptic", OpenXRAction::OPENXR_ACTION_HAPTIC, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> haptic = action_set->add_new_action("haptic", "Haptic", OpenXRAction::OPENXR_ACTION_HAPTIC, + "/user/hand/left," + "/user/hand/right," + // "/user/vive_tracker_htcx/role/handheld_object," <-- getting errors on this one + "/user/vive_tracker_htcx/role/left_foot," + "/user/vive_tracker_htcx/role/right_foot," + "/user/vive_tracker_htcx/role/left_shoulder," + "/user/vive_tracker_htcx/role/right_shoulder," + "/user/vive_tracker_htcx/role/left_elbow," + "/user/vive_tracker_htcx/role/right_elbow," + "/user/vive_tracker_htcx/role/left_knee," + "/user/vive_tracker_htcx/role/right_knee," + "/user/vive_tracker_htcx/role/waist," + "/user/vive_tracker_htcx/role/chest," + "/user/vive_tracker_htcx/role/camera," + "/user/vive_tracker_htcx/role/keyboard"); // Create our interaction profiles Ref<OpenXRInteractionProfile> profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/khr/simple_controller"); @@ -165,7 +257,7 @@ void OpenXRActionMap::create_default_action_sets() { profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click"); // wmr controller has no a/b/x/y buttons profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); - profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // OpenXR will conver float to bool + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // OpenXR will convert float to bool profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); // OpenXR will convert bool to float profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); // primary on our wmr controller is our thumbstick, no touch @@ -178,27 +270,6 @@ void OpenXRActionMap::create_default_action_sets() { profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); add_interaction_profile(profile); - // Create our HP MR controller profile - profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/hp/mixed_reality_controller"); - profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); - profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); - profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); - // hpmr controllers have no select button we can use - profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click"); - // hpmr controllers only register click, not touch, on our a/b/x/y buttons - profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand - profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand - profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); - profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); - profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); - profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); - // primary on our hpmr controller is our thumbstick - profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); - profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); - // No secondary on our hpmr controller - profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); - add_interaction_profile(profile); - // Create our Meta touch controller profile profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/oculus/touch_controller"); profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); @@ -249,12 +320,216 @@ void OpenXRActionMap::create_default_action_sets() { profile->add_new_binding(secondary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); add_interaction_profile(profile); + + // Note, the following profiles are all part of extensions. + // We include these regardless of whether the extension is active. + // We want our action map to be as complete as possible so our game is as portable as possible. + // It is very possible these will in due time become core. + + // Create our HP MR controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/hp/mixed_reality_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + // hpmr controllers have no select button we can use + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click"); + // hpmr controllers only register click, not touch, on our a/b/x/y buttons + profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand + profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + // primary on our hpmr controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + // No secondary on our hpmr controller + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Samsung Odyssey controller profile, + // Note that this controller is only identified specifically on WMR, on SteamVR this is identified as a normal WMR controller. + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/samsung/odyssey_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + // Odyssey controllers have no select button we can use + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click"); + // Odyssey controller has no a/b/x/y buttons + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + // primary on our Odyssey controller is our thumbstick, no touch + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + // secondary on our Odyssey controller is our trackpad + profile->add_new_binding(secondary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad"); + profile->add_new_binding(secondary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click"); + profile->add_new_binding(secondary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Vive Cosmos controller + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_cosmos_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click"); + profile->add_new_binding(select_button, "/user/hand/left/input/system/click"); // we'll map system to select + profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand + profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + // primary on our Cosmos controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch"); + // No secondary on our cosmos controller + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Vive Focus 3 controller + // Note, Vive Focus 3 currently is not yet supported as a stand alone device + // however HTC currently has a beta OpenXR runtime in testing we may support in the near future + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_focus3_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click"); + profile->add_new_binding(select_button, "/user/hand/left/input/system/click"); // we'll map system to select + profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand + profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click"); + profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + // primary on our Focus 3 controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch"); + // We only have a thumb rest + profile->add_new_binding(secondary_touch, "/user/hand/left/input/thumbrest/touch,/user/hand/right/input/thumbrest/touch"); + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Huawei controller + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/huawei/controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(menu_button, "/user/hand/left/input/home/click,/user/hand/right/input/home/click"); + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click"); + // primary on our Huawei controller is our trackpad + profile->add_new_binding(primary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad"); + profile->add_new_binding(primary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + + // Create our HTC Vive tracker profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_tracker_htcx"); + profile->add_new_binding(default_pose, + // "/user/vive_tracker_htcx/role/handheld_object/input/grip/pose," <-- getting errors on this one + "/user/vive_tracker_htcx/role/left_foot/input/grip/pose," + "/user/vive_tracker_htcx/role/right_foot/input/grip/pose," + "/user/vive_tracker_htcx/role/left_shoulder/input/grip/pose," + "/user/vive_tracker_htcx/role/right_shoulder/input/grip/pose," + "/user/vive_tracker_htcx/role/left_elbow/input/grip/pose," + "/user/vive_tracker_htcx/role/right_elbow/input/grip/pose," + "/user/vive_tracker_htcx/role/left_knee/input/grip/pose," + "/user/vive_tracker_htcx/role/right_knee/input/grip/pose," + "/user/vive_tracker_htcx/role/waist/input/grip/pose," + "/user/vive_tracker_htcx/role/chest/input/grip/pose," + "/user/vive_tracker_htcx/role/camera/input/grip/pose," + "/user/vive_tracker_htcx/role/keyboard/input/grip/pose"); + profile->add_new_binding(haptic, + // "/user/vive_tracker_htcx/role/handheld_object/output/haptic," <-- getting errors on this one + "/user/vive_tracker_htcx/role/left_foot/output/haptic," + "/user/vive_tracker_htcx/role/right_foot/output/haptic," + "/user/vive_tracker_htcx/role/left_shoulder/output/haptic," + "/user/vive_tracker_htcx/role/right_shoulder/output/haptic," + "/user/vive_tracker_htcx/role/left_elbow/output/haptic," + "/user/vive_tracker_htcx/role/right_elbow/output/haptic," + "/user/vive_tracker_htcx/role/left_knee/output/haptic," + "/user/vive_tracker_htcx/role/right_knee/output/haptic," + "/user/vive_tracker_htcx/role/waist/output/haptic," + "/user/vive_tracker_htcx/role/chest/output/haptic," + "/user/vive_tracker_htcx/role/camera/output/haptic," + "/user/vive_tracker_htcx/role/keyboard/output/haptic"); + add_interaction_profile(profile); } void OpenXRActionMap::create_editor_action_sets() { // TODO implement } +Ref<OpenXRAction> OpenXRActionMap::get_action(const String p_path) const { + PackedStringArray paths = p_path.split("/", false); + ERR_FAIL_COND_V(paths.size() != 2, Ref<OpenXRAction>()); + + Ref<OpenXRActionSet> action_set = find_action_set(paths[0]); + if (action_set.is_valid()) { + return action_set->get_action(paths[1]); + } + + return Ref<OpenXRAction>(); +} + +void OpenXRActionMap::remove_action(const String p_path) { + Ref<OpenXRAction> action = get_action(p_path); + if (action.is_valid()) { + OpenXRActionSet *action_set = action->get_action_set(); + if (action_set != nullptr) { + // Remove the action from this action set + action_set->remove_action(action); + } + + for (int i = 0; i < interaction_profiles.size(); i++) { + Ref<OpenXRInteractionProfile> interaction_profile = interaction_profiles[i]; + + // Remove any bindings for this action + interaction_profile->remove_binding_for_action(action); + } + } +} + +PackedStringArray OpenXRActionMap::get_top_level_paths(Ref<OpenXRAction> p_action) { + PackedStringArray arr; + + for (int i = 0; i < interaction_profiles.size(); i++) { + Ref<OpenXRInteractionProfile> ip = interaction_profiles[i]; + const OpenXRDefs::InteractionProfile *profile = OpenXRDefs::get_profile(ip->get_interaction_profile_path()); + + if (profile != nullptr) { + for (int j = 0; j < ip->get_binding_count(); j++) { + Ref<OpenXRIPBinding> binding = ip->get_binding(j); + if (binding->get_action() == p_action) { + PackedStringArray paths = binding->get_paths(); + + for (int k = 0; k < paths.size(); k++) { + const OpenXRDefs::IOPath *io_path = profile->get_io_path(paths[k]); + if (io_path != nullptr) { + String top_path = String(io_path->top_level_path->openxr_path); + + if (!arr.has(top_path)) { + arr.push_back(top_path); + } + } + } + } + } + } + } + + print_line("Toplevel paths for", p_action->get_name_with_set(), "are", arr); + + return arr; +} + OpenXRActionMap::~OpenXRActionMap() { action_sets.clear(); interaction_profiles.clear(); diff --git a/modules/openxr/action_map/openxr_action_map.h b/modules/openxr/action_map/openxr_action_map.h index 866e170468..8659cd3942 100644 --- a/modules/openxr/action_map/openxr_action_map.h +++ b/modules/openxr/action_map/openxr_action_map.h @@ -28,11 +28,12 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef OPENXR_ACTION_SETS_H -#define OPENXR_ACTION_SETS_H +#ifndef OPENXR_ACTION_MAP_H +#define OPENXR_ACTION_MAP_H #include "core/io/resource.h" +#include "openxr_action.h" #include "openxr_action_set.h" #include "openxr_interaction_profile.h" @@ -47,22 +48,35 @@ protected: static void _bind_methods(); public: - void set_action_sets(Array p_action_sets); - Array get_action_sets() const; + void set_action_sets(Array p_action_sets); // Set our actions sets by providing an array with action sets (for loading from resource) + Array get_action_sets() const; // Get our action sets as an array (for saving to resource) - void add_action_set(Ref<OpenXRActionSet> p_action_set); - void remove_action_set(Ref<OpenXRActionSet> p_action_set); + int get_action_set_count() const; // Retrieve the number of action sets we have + Ref<OpenXRActionSet> find_action_set(String p_name) const; // Find an action set by name + Ref<OpenXRActionSet> get_action_set(int p_idx) const; // Retrieve an action set by index + void add_action_set(Ref<OpenXRActionSet> p_action_set); // Add an action set to our action map + void remove_action_set(Ref<OpenXRActionSet> p_action_set); // Remove an action set from our action map - void set_interaction_profiles(Array p_interaction_profiles); - Array get_interaction_profiles() const; + 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) - void add_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile); - void remove_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile); + int get_interaction_profile_count() const; // Retrieve the number of interaction profiles we have + Ref<OpenXRInteractionProfile> find_interaction_profile(String p_path) const; // Find an interaction profile by path + Ref<OpenXRInteractionProfile> get_interaction_profile(int p_idx) const; // Retrieve an interaction profile by index + void add_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile); // Add an interaction profile to our action map + void remove_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile); // remove an interaction profile from our action map - void create_default_action_sets(); - void create_editor_action_sets(); + void create_default_action_sets(); // Create our default action set for runtime + void create_editor_action_sets(); // Create our action set for the editor + + // Helper functions for editor + Ref<OpenXRAction> get_action(const String p_path) const; // Retrieve an action using <action name>/<action> as our parameter + void remove_action(const String p_path); // Remove action from action set, also removes it from interaction profiles + PackedStringArray get_top_level_paths(Ref<OpenXRAction> p_action); // Determines the top level paths based on where an action is bound in interaction profiles + + // TODO add validation to display in the interface that checks if we have action sets with the same name or if we have interaction profiles for the same path ~OpenXRActionMap(); }; -#endif // !OPENXR_ACTION_SETS_H +#endif // OPENXR_ACTION_MAP_H diff --git a/modules/openxr/action_map/openxr_action_set.cpp b/modules/openxr/action_map/openxr_action_set.cpp index 465a709b60..be45218300 100644 --- a/modules/openxr/action_map/openxr_action_set.cpp +++ b/modules/openxr/action_map/openxr_action_set.cpp @@ -39,6 +39,7 @@ void OpenXRActionSet::_bind_methods() { ClassDB::bind_method(D_METHOD("get_priority"), &OpenXRActionSet::get_priority); ADD_PROPERTY(PropertyInfo(Variant::INT, "priority"), "set_priority", "get_priority"); + ClassDB::bind_method(D_METHOD("get_action_count"), &OpenXRActionSet::get_action_count); ClassDB::bind_method(D_METHOD("set_actions", "actions"), &OpenXRActionSet::set_actions); ClassDB::bind_method(D_METHOD("get_actions"), &OpenXRActionSet::get_actions); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "actions", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRAction", PROPERTY_USAGE_NO_EDITOR), "set_actions", "get_actions"); @@ -75,18 +76,54 @@ int OpenXRActionSet::get_priority() const { return priority; } +int OpenXRActionSet::get_action_count() const { + return actions.size(); +} + +void OpenXRActionSet::clear_actions() { + // Actions held within our action set should be released and destroyed but just in case they are still used some where else + for (int i = 0; i < actions.size(); i++) { + Ref<OpenXRAction> action = actions[i]; + action->action_set = nullptr; + } + actions.clear(); +} + void OpenXRActionSet::set_actions(Array p_actions) { - actions = p_actions; + // Any actions not retained in p_actions should be freed automatically, those held within our Array will have be relinked to our action set. + clear_actions(); + + for (int i = 0; i < p_actions.size(); i++) { + // add them anew so we verify our action_set pointer + add_action(p_actions[i]); + } } Array OpenXRActionSet::get_actions() const { return actions; } +Ref<OpenXRAction> OpenXRActionSet::get_action(const String p_name) const { + for (int i = 0; i < actions.size(); i++) { + Ref<OpenXRAction> action = actions[i]; + if (action->get_name() == p_name) { + return action; + } + } + + return Ref<OpenXRAction>(); +} + void OpenXRActionSet::add_action(Ref<OpenXRAction> p_action) { ERR_FAIL_COND(p_action.is_null()); if (actions.find(p_action) == -1) { + if (p_action->action_set && p_action->action_set != this) { + // action should only relate to our action set + p_action->action_set->remove_action(p_action); + } + + p_action->action_set = this; actions.push_back(p_action); } } @@ -95,6 +132,9 @@ void OpenXRActionSet::remove_action(Ref<OpenXRAction> p_action) { int idx = actions.find(p_action); if (idx != -1) { actions.remove_at(idx); + + ERR_FAIL_COND_MSG(p_action->action_set != this, "Removing action that belongs to this action set but had incorrect action set pointer."); // this should never happen! + p_action->action_set = nullptr; } } @@ -107,5 +147,5 @@ Ref<OpenXRAction> OpenXRActionSet::add_new_action(const char *p_name, const char } OpenXRActionSet::~OpenXRActionSet() { - actions.clear(); + clear_actions(); } diff --git a/modules/openxr/action_map/openxr_action_set.h b/modules/openxr/action_map/openxr_action_set.h index 012a088b1c..2ef7ba4c32 100644 --- a/modules/openxr/action_map/openxr_action_set.h +++ b/modules/openxr/action_map/openxr_action_set.h @@ -43,28 +43,33 @@ private: int priority = 0; Array actions; + void clear_actions(); protected: static void _bind_methods(); public: - static Ref<OpenXRActionSet> new_action_set(const char *p_name, const char *p_localized_name, const int p_priority = 0); + static Ref<OpenXRActionSet> new_action_set(const char *p_name, const char *p_localized_name, const int p_priority = 0); // Helper function for adding and setting up an action set - void set_localized_name(const String p_localized_name); - String get_localized_name() const; + void set_localized_name(const String p_localized_name); // Set the localized name of this action set + String get_localized_name() const; // Get the localized name of this action set - void set_priority(const int p_priority); - int get_priority() const; + void set_priority(const int p_priority); // Set the priority of this action set + int get_priority() const; // Get the priority of this action set - void set_actions(Array p_actions); - Array get_actions() const; + int get_action_count() const; // Retrieve the number of actions in our action set + void set_actions(Array p_actions); // Set our actions using an array of actions (for loading a resource) + Array get_actions() const; // Get our actions as an array (for saving a resource) - void add_action(Ref<OpenXRAction> p_action); - void remove_action(Ref<OpenXRAction> p_action); + Ref<OpenXRAction> get_action(const String p_name) const; // Retrieve an action by name + void add_action(Ref<OpenXRAction> p_action); // Add a new action to our action set + void remove_action(Ref<OpenXRAction> p_action); // remove a action from our action set - Ref<OpenXRAction> add_new_action(const char *p_name, const char *p_localized_name, const OpenXRAction::ActionType p_action_type, const char *p_toplevel_paths); + Ref<OpenXRAction> add_new_action(const char *p_name, const char *p_localized_name, const OpenXRAction::ActionType p_action_type, const char *p_toplevel_paths); // Helper function for adding and setting up an action + + // TODO add validation to display in the interface that checks if we have duplicate action names within our action set ~OpenXRActionSet(); }; -#endif // !OPENXR_ACTION_SET_H +#endif // OPENXR_ACTION_SET_H diff --git a/modules/openxr/action_map/openxr_defs.cpp b/modules/openxr/action_map/openxr_defs.cpp new file mode 100644 index 0000000000..89860199be --- /dev/null +++ b/modules/openxr/action_map/openxr_defs.cpp @@ -0,0 +1,651 @@ +/*************************************************************************/ +/* openxr_defs.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_defs.h" + +// Our top level paths to which devices can be bound +OpenXRDefs::TopLevelPath OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_TOP_LEVEL_PATH_MAX] = { + // Core OpenXR paths + { "Left hand controller", "/user/hand/left" }, + { "Right hand controller", "/user/hand/right" }, + { "Head", "/user/head" }, + { "Gamepad", "/user/gamepad" }, + { "Treadmill", "/user/treadmill" }, + + // Specific to HTC tracker extension + // { "Handheld object tracker", "/user/vive_tracker_htcx/role/handheld_object" }, + { "Left foot tracker", "/user/vive_tracker_htcx/role/left_foot" }, + { "Right foot tracker", "/user/vive_tracker_htcx/role/right_foot" }, + { "Left shoulder tracker", "/user/vive_tracker_htcx/role/left_shoulder" }, + { "Right shoulder tracker", "/user/vive_tracker_htcx/role/right_shoulder" }, + { "Left elbow tracker", "/user/vive_tracker_htcx/role/left_elbow" }, + { "Right elbow tracker", "/user/vive_tracker_htcx/role/right_elbow" }, + { "Left knee tracker", "/user/vive_tracker_htcx/role/left_knee" }, + { "Right knee tracker", "/user/vive_tracker_htcx/role/right_knee" }, + { "Waist tracker", "/user/vive_tracker_htcx/role/waist" }, + { "Chest tracker", "/user/vive_tracker_htcx/role/chest" }, + { "Camera tracker", "/user/vive_tracker_htcx/role/camera" }, + { "Keyboard tracker", "/user/vive_tracker_htcx/role/keyboard" }, + +}; + +// Fallback Khronos simple controller +OpenXRDefs::IOPath OpenXRDefs::simple_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Select click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/select/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Select click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/select/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Original HTC Vive wands +OpenXRDefs::IOPath OpenXRDefs::vive_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "System click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/system/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "System click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/system/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Microsoft motion controller (original WMR controllers) +OpenXRDefs::IOPath OpenXRDefs::motion_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// HP MR controller (newer G2 controllers) +OpenXRDefs::IOPath OpenXRDefs::hpmr_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "X click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/x/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Y click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/y/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Squeeze", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Meta touch controller (original touch controllers, Quest 1 and Quest 2 controllers) +OpenXRDefs::IOPath OpenXRDefs::touch_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "System click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/system/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "X click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/x/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "X touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/x/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Y click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/y/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Y touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/y/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Squeeze", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Valve index controller +OpenXRDefs::IOPath OpenXRDefs::index_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + + { "System click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/system/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "System click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/system/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "A click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/a/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/a/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/b/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/b/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Squeeze", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad force", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/force", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad force", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/force", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Samsung odyssey controller +OpenXRDefs::IOPath OpenXRDefs::odyssey_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Vive Cosmos controller +OpenXRDefs::IOPath OpenXRDefs::vive_cosmos_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "System click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/system/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "X click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/x/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Y click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/y/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Shoulder click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/right/input/shoulder/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Shoulder click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/shoulder/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Vive Focus 3 controller +OpenXRDefs::IOPath OpenXRDefs::vive_focus3_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "System click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/system/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "X click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/x/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Y click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/y/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/touch ", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Thumbrest touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbrest/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Huawei controller +OpenXRDefs::IOPath OpenXRDefs::huawei_controller_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + + { "Home click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/home/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Home click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/home/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Back click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/back/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Back click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/back/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Volume up click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/volume_up/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Volume up click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/volume_up/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Volume down click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/volume_down/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Volume down click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/volume_down/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// HTC Vive tracker +// Interestingly enough trackers don't have buttons or inputs, yet these are defined in the spec. +// I think this can be supported through attachments on the trackers. +OpenXRDefs::IOPath OpenXRDefs::vive_tracker_controller_paths[] = { + // { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_HANDHELD_TRACKER], "/user/vive_tracker_htcx/role/handheld_object/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/left_foot/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/right_foot/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/left_shoulder/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/right_shoulder/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/left_elbow/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/right_elbow/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/left_knee/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/right_knee/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_WAIST_TRACKER], "/user/vive_tracker_htcx/role/waist/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CHEST_TRACKER], "/user/vive_tracker_htcx/role/chest/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CAMERA_TRACKER], "/user/vive_tracker_htcx/role/camera/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_KEYBOARD_TRACKER], "/user/vive_tracker_htcx/role/keyboard/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + // { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_HANDHELD_TRACKER], "/user/vive_tracker_htcx/role/handheld_object/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/left_foot/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/right_foot/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/left_shoulder/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/right_shoulder/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/left_elbow/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/right_elbow/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/left_knee/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/right_knee/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_WAIST_TRACKER], "/user/vive_tracker_htcx/role/waist/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CHEST_TRACKER], "/user/vive_tracker_htcx/role/chest/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CAMERA_TRACKER], "/user/vive_tracker_htcx/role/camera/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_KEYBOARD_TRACKER], "/user/vive_tracker_htcx/role/keyboard/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + + // { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_HANDHELD_TRACKER], "/user/vive_tracker_htcx/role/handheld_object/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/left_foot/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/right_foot/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/left_shoulder/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/right_shoulder/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/left_elbow/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/right_elbow/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/left_knee/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/right_knee/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_WAIST_TRACKER], "/user/vive_tracker_htcx/role/waist/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CHEST_TRACKER], "/user/vive_tracker_htcx/role/chest/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CAMERA_TRACKER], "/user/vive_tracker_htcx/role/camera/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_KEYBOARD_TRACKER], "/user/vive_tracker_htcx/role/keyboard/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + // { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_HANDHELD_TRACKER], "/user/vive_tracker_htcx/role/handheld_object/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/left_foot/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/right_foot/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/left_shoulder/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/right_shoulder/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/left_elbow/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/right_elbow/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/left_knee/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/right_knee/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_WAIST_TRACKER], "/user/vive_tracker_htcx/role/waist/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CHEST_TRACKER], "/user/vive_tracker_htcx/role/chest/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CAMERA_TRACKER], "/user/vive_tracker_htcx/role/camera/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_KEYBOARD_TRACKER], "/user/vive_tracker_htcx/role/keyboard/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + // { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_HANDHELD_TRACKER], "/user/vive_tracker_htcx/role/handheld_object/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/left_foot/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/right_foot/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/left_shoulder/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/right_shoulder/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/left_elbow/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/right_elbow/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/left_knee/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/right_knee/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_WAIST_TRACKER], "/user/vive_tracker_htcx/role/waist/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CHEST_TRACKER], "/user/vive_tracker_htcx/role/chest/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CAMERA_TRACKER], "/user/vive_tracker_htcx/role/camera/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_KEYBOARD_TRACKER], "/user/vive_tracker_htcx/role/keyboard/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + + // { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_HANDHELD_TRACKER], "/user/vive_tracker_htcx/role/handheld_object/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/left_foot/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/right_foot/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/left_shoulder/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/right_shoulder/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/left_elbow/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/right_elbow/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/left_knee/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/right_knee/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_WAIST_TRACKER], "/user/vive_tracker_htcx/role/waist/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CHEST_TRACKER], "/user/vive_tracker_htcx/role/chest/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CAMERA_TRACKER], "/user/vive_tracker_htcx/role/camera/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_KEYBOARD_TRACKER], "/user/vive_tracker_htcx/role/keyboard/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + // { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_HANDHELD_TRACKER], "/user/vive_tracker_htcx/role/handheld_object/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/left_foot/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/right_foot/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/left_shoulder/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/right_shoulder/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/left_elbow/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/right_elbow/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/left_knee/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/right_knee/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_WAIST_TRACKER], "/user/vive_tracker_htcx/role/waist/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CHEST_TRACKER], "/user/vive_tracker_htcx/role/chest/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CAMERA_TRACKER], "/user/vive_tracker_htcx/role/camera/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_KEYBOARD_TRACKER], "/user/vive_tracker_htcx/role/keyboard/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + // { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_HANDHELD_TRACKER], "/user/vive_tracker_htcx/role/handheld_object/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/left_foot/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/right_foot/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/left_shoulder/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/right_shoulder/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/left_elbow/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/right_elbow/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/left_knee/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/right_knee/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_WAIST_TRACKER], "/user/vive_tracker_htcx/role/waist/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CHEST_TRACKER], "/user/vive_tracker_htcx/role/chest/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CAMERA_TRACKER], "/user/vive_tracker_htcx/role/camera/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_KEYBOARD_TRACKER], "/user/vive_tracker_htcx/role/keyboard/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + + // { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_HANDHELD_TRACKER], "/user/vive_tracker_htcx/role/handheld_object/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/left_foot/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_FOOT_TRACKER], "/user/vive_tracker_htcx/role/right_foot/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/left_shoulder/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_SHOULDER_TRACKER], "/user/vive_tracker_htcx/role/right_shoulder/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/left_elbow/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_ELBOW_TRACKER], "/user/vive_tracker_htcx/role/right_elbow/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_LEFT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/left_knee/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_RIGHT_KNEE_TRACKER], "/user/vive_tracker_htcx/role/right_knee/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_WAIST_TRACKER], "/user/vive_tracker_htcx/role/waist/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CHEST_TRACKER], "/user/vive_tracker_htcx/role/chest/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_CAMERA_TRACKER], "/user/vive_tracker_htcx/role/camera/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_HTC_KEYBOARD_TRACKER], "/user/vive_tracker_htcx/role/keyboard/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +OpenXRDefs::InteractionProfile OpenXRDefs::available_interaction_profiles[] = { + { + "Simple controller", // display_name + "/interaction_profiles/khr/simple_controller", // openxr_path + simple_io_paths, // io_paths + sizeof(simple_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "HTC Vive wand", // display_name + "/interaction_profiles/htc/vive_controller", // openxr_path + vive_io_paths, // io_paths + sizeof(vive_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "MS Motion controller", // display_name + "/interaction_profiles/microsoft/motion_controller", // openxr_path + motion_io_paths, // io_paths + sizeof(motion_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "HPMR controller", // display_name + "/interaction_profiles/hp/mixed_reality_controller", // openxr_path + hpmr_io_paths, // io_paths + sizeof(hpmr_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "Touch controller", // display_name + "/interaction_profiles/oculus/touch_controller", // openxr_path + touch_io_paths, // io_paths + sizeof(touch_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "Index controller", // display_name + "/interaction_profiles/valve/index_controller", // openxr_path + index_io_paths, // io_paths + sizeof(index_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "Samsung Odyssey controller", // display_name + "/interaction_profiles/samsung/odyssey_controller", // openxr_path + odyssey_io_paths, // io_paths + sizeof(odyssey_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "Vive Cosmos controller", // display_name + "/interaction_profiles/htc/vive_cosmos_controller", // openxr_path + vive_cosmos_paths, // io_paths + sizeof(vive_cosmos_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "Vive Focus 3 controller", // display_name + "/interaction_profiles/htc/vive_focus3_controller", // openxr_path + vive_focus3_paths, // io_paths + sizeof(vive_focus3_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "Huawei controller", // display_name + "/interaction_profiles/huawei/controller", // openxr_path + huawei_controller_paths, // io_paths + sizeof(huawei_controller_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + + { + "HTC Vive tracker", // display_name + "/interaction_profiles/htc/vive_tracker_htcx", // openxr_path + vive_tracker_controller_paths, // io_paths + sizeof(vive_tracker_controller_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, +}; + +int OpenXRDefs::available_interaction_profile_count = sizeof(OpenXRDefs::available_interaction_profiles) / sizeof(OpenXRDefs::InteractionProfile); + +const OpenXRDefs::TopLevelPath *OpenXRDefs::get_top_level_path(const String p_top_level_path) { + for (int i = 0; i < OPENXR_TOP_LEVEL_PATH_MAX; i++) { + if (available_top_level_paths[i].openxr_path == p_top_level_path) { + return &OpenXRDefs::available_top_level_paths[i]; + } + } + + return nullptr; +} + +const OpenXRDefs::InteractionProfile *OpenXRDefs::get_profile(const String p_interaction_profile_path) { + for (int i = 0; i < available_interaction_profile_count; i++) { + if (available_interaction_profiles[i].openxr_path == p_interaction_profile_path) { + return &available_interaction_profiles[i]; + } + } + + return nullptr; +} + +const OpenXRDefs::IOPath *OpenXRDefs::InteractionProfile::get_io_path(const String p_io_path) const { + for (int i = 0; i < available_interaction_profiles[i].io_path_count; i++) { + if (io_paths[i].openxr_path == p_io_path) { + return &io_paths[i]; + } + } + + return nullptr; +} + +const OpenXRDefs::IOPath *OpenXRDefs::get_io_path(const String p_interaction_profile_path, const String p_io_path) { + const OpenXRDefs::InteractionProfile *profile = OpenXRDefs::get_profile(p_interaction_profile_path); + if (profile != nullptr) { + return profile->get_io_path(p_io_path); + } + + return nullptr; +} + +PackedStringArray OpenXRDefs::get_interaction_profile_paths() { + PackedStringArray arr; + + for (int i = 0; i < available_interaction_profile_count; i++) { + arr.push_back(available_interaction_profiles[i].openxr_path); + } + + return arr; +} diff --git a/modules/openxr/action_map/openxr_defs.h b/modules/openxr/action_map/openxr_defs.h new file mode 100644 index 0000000000..446e6eb9c6 --- /dev/null +++ b/modules/openxr/action_map/openxr_defs.h @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* openxr_defs.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_DEFS_H +#define OPENXR_DEFS_H + +#include "openxr_action.h" + +/////////////////////////////////////////////////////////////////////////// +// Stores available interaction profiles +// +// OpenXR defines and hardcodes all the supported input devices and their +// paths as part of the OpenXR spec. When support for new devices is +// introduced this often starts life as extensions that need to be enabled +// until they are adopted into the core. As there is no interface to +// enumerate the possibly paths, and that any OpenXR runtime would likely +// limit such enumeration to those input devices supported by that runtime +// there is no other option than to hardcode this. +// +// Note on action type that automatic conversions between boolean and float +// are supported but otherwise action types should match between action and +// input/output paths. + +class OpenXRDefs { +public: + enum TOP_LEVEL_PATH { + // Core OpenXR toplevel paths + OPENXR_LEFT_HAND, + OPENXR_RIGHT_HAND, + OPENXR_HEAD, + OPENXR_GAMEPAD, + OPENXR_TREADMILL, + + // HTC tracker extension toplevel paths + // OPENXR_HTC_HANDHELD_TRACKER, + OPENXR_HTC_LEFT_FOOT_TRACKER, + OPENXR_HTC_RIGHT_FOOT_TRACKER, + OPENXR_HTC_LEFT_SHOULDER_TRACKER, + OPENXR_HTC_RIGHT_SHOULDER_TRACKER, + OPENXR_HTC_LEFT_ELBOW_TRACKER, + OPENXR_HTC_RIGHT_ELBOW_TRACKER, + OPENXR_HTC_LEFT_KNEE_TRACKER, + OPENXR_HTC_RIGHT_KNEE_TRACKER, + OPENXR_HTC_WAIST_TRACKER, + OPENXR_HTC_CHEST_TRACKER, + OPENXR_HTC_CAMERA_TRACKER, + OPENXR_HTC_KEYBOARD_TRACKER, + + OPENXR_TOP_LEVEL_PATH_MAX + }; + + struct TopLevelPath { + const char *display_name; // User friendly display name (i.e. Left controller) + const char *openxr_path; // Path in OpenXR (i.e. /user/hand/left) + }; + + struct IOPath { + const char *display_name; // User friendly display name (i.e. Grip pose (left controller)) + const TopLevelPath *top_level_path; // Top level path identifying the usage of the device in relation to this input/output + const char *openxr_path; // Path in OpenXR (i.e. /user/hand/left/input/grip/pose) + const OpenXRAction::ActionType action_type; // Type of input/output + }; + + struct InteractionProfile { + const char *display_name; // User friendly display name (i.e. Simple controller) + const char *openxr_path; // Path in OpenXR (i.e. /interaction_profiles/khr/simple_controller) + const IOPath *io_paths; // Inputs and outputs for this device + const int io_path_count; // Number of inputs and outputs for this device + + const IOPath *get_io_path(const String p_io_path) const; + }; + +private: + static TopLevelPath available_top_level_paths[OPENXR_TOP_LEVEL_PATH_MAX]; + static IOPath simple_io_paths[]; + static IOPath vive_io_paths[]; + static IOPath motion_io_paths[]; + static IOPath hpmr_io_paths[]; + static IOPath touch_io_paths[]; + static IOPath index_io_paths[]; + static IOPath odyssey_io_paths[]; + static IOPath vive_cosmos_paths[]; + static IOPath vive_focus3_paths[]; + static IOPath huawei_controller_paths[]; + static IOPath vive_tracker_controller_paths[]; + static InteractionProfile available_interaction_profiles[]; + static int available_interaction_profile_count; + +public: + static const TopLevelPath *get_top_level_path(const String p_top_level_path); + static const InteractionProfile *get_profile(const String p_interaction_profile_path); + static const IOPath *get_io_path(const String p_interaction_profile_path, const String p_io_path); + + static PackedStringArray get_interaction_profile_paths(); +}; + +#endif // OPENXR_DEFS_H diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp index bc33814f17..99d7a17acf 100644 --- a/modules/openxr/action_map/openxr_interaction_profile.cpp +++ b/modules/openxr/action_map/openxr_interaction_profile.cpp @@ -35,9 +35,14 @@ void OpenXRIPBinding::_bind_methods() { ClassDB::bind_method(D_METHOD("get_action"), &OpenXRIPBinding::get_action); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "action", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRAction"), "set_action", "get_action"); + ClassDB::bind_method(D_METHOD("get_path_count"), &OpenXRIPBinding::get_path_count); ClassDB::bind_method(D_METHOD("set_paths", "paths"), &OpenXRIPBinding::set_paths); ClassDB::bind_method(D_METHOD("get_paths"), &OpenXRIPBinding::get_paths); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "paths", PROPERTY_HINT_ARRAY_TYPE, "STRING"), "set_paths", "get_paths"); + + ClassDB::bind_method(D_METHOD("has_path", "path"), &OpenXRIPBinding::has_path); + ClassDB::bind_method(D_METHOD("add_path", "path"), &OpenXRIPBinding::add_path); + ClassDB::bind_method(D_METHOD("remove_path", "path"), &OpenXRIPBinding::remove_path); } Ref<OpenXRIPBinding> OpenXRIPBinding::new_binding(const Ref<OpenXRAction> p_action, const char *p_paths) { @@ -59,6 +64,10 @@ Ref<OpenXRAction> OpenXRIPBinding::get_action() const { return action; } +int OpenXRIPBinding::get_path_count() const { + return paths.size(); +} + void OpenXRIPBinding::set_paths(const PackedStringArray p_paths) { paths = p_paths; } @@ -71,6 +80,22 @@ void OpenXRIPBinding::parse_paths(const String p_paths) { paths = p_paths.split(",", false); } +bool OpenXRIPBinding::has_path(const String p_path) const { + return paths.has(p_path); +} + +void OpenXRIPBinding::add_path(const String p_path) { + if (!paths.has(p_path)) { + paths.push_back(p_path); + } +} + +void OpenXRIPBinding::remove_path(const String p_path) { + if (paths.has(p_path)) { + paths.erase(p_path); + } +} + OpenXRIPBinding::~OpenXRIPBinding() { action.unref(); } @@ -80,6 +105,8 @@ void OpenXRInteractionProfile::_bind_methods() { ClassDB::bind_method(D_METHOD("get_interaction_profile_path"), &OpenXRInteractionProfile::get_interaction_profile_path); ADD_PROPERTY(PropertyInfo(Variant::STRING, "interaction_profile_path"), "set_interaction_profile_path", "get_interaction_profile_path"); + ClassDB::bind_method(D_METHOD("get_binding_count"), &OpenXRInteractionProfile::get_binding_count); + ClassDB::bind_method(D_METHOD("get_binding", "index"), &OpenXRInteractionProfile::get_binding); 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"); @@ -101,18 +128,43 @@ String OpenXRInteractionProfile::get_interaction_profile_path() const { return interaction_profile_path; } +int OpenXRInteractionProfile::get_binding_count() const { + return bindings.size(); +} + +Ref<OpenXRIPBinding> OpenXRInteractionProfile::get_binding(int p_index) const { + ERR_FAIL_INDEX_V(p_index, bindings.size(), Ref<OpenXRIPBinding>()); + + return bindings[p_index]; +} + void OpenXRInteractionProfile::set_bindings(Array p_bindings) { bindings = p_bindings; + + // TODO add check here that our bindings don't contain duplicate actions } Array OpenXRInteractionProfile::get_bindings() const { return bindings; } +Ref<OpenXRIPBinding> OpenXRInteractionProfile::get_binding_for_action(const Ref<OpenXRAction> p_action) const { + for (int i = 0; i < bindings.size(); i++) { + Ref<OpenXRIPBinding> binding = bindings[i]; + if (binding->get_action() == p_action) { + return binding; + } + } + + return Ref<OpenXRIPBinding>(); +} + void OpenXRInteractionProfile::add_binding(Ref<OpenXRIPBinding> p_binding) { ERR_FAIL_COND(p_binding.is_null()); if (bindings.find(p_binding) == -1) { + ERR_FAIL_COND_MSG(get_binding_for_action(p_binding->get_action()).is_valid(), "There is already a binding for this action in this interaction profile"); + bindings.push_back(p_binding); } } @@ -131,6 +183,15 @@ void OpenXRInteractionProfile::add_new_binding(const Ref<OpenXRAction> p_action, add_binding(binding); } +void OpenXRInteractionProfile::remove_binding_for_action(const Ref<OpenXRAction> p_action) { + for (int i = bindings.size() - 1; i >= 0; i--) { + Ref<OpenXRIPBinding> binding = bindings[i]; + if (binding->get_action() == p_action) { + remove_binding(binding); + } + } +} + OpenXRInteractionProfile::~OpenXRInteractionProfile() { bindings.clear(); } diff --git a/modules/openxr/action_map/openxr_interaction_profile.h b/modules/openxr/action_map/openxr_interaction_profile.h index abbc429e7d..c77fd490bb 100644 --- a/modules/openxr/action_map/openxr_interaction_profile.h +++ b/modules/openxr/action_map/openxr_interaction_profile.h @@ -34,6 +34,7 @@ #include "core/io/resource.h" #include "openxr_action.h" +#include "openxr_defs.h" class OpenXRIPBinding : public Resource { GDCLASS(OpenXRIPBinding, Resource); @@ -46,15 +47,22 @@ protected: static void _bind_methods(); public: - static Ref<OpenXRIPBinding> new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); + static Ref<OpenXRIPBinding> new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); // Helper function for adding a new binding - void set_action(const Ref<OpenXRAction> p_action); - Ref<OpenXRAction> get_action() const; + void set_action(const Ref<OpenXRAction> p_action); // Set the action for this binding + Ref<OpenXRAction> get_action() const; // Get the action for this binding - void set_paths(const PackedStringArray p_paths); - PackedStringArray get_paths() const; + int get_path_count() const; // Get the number of io paths + 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) - void parse_paths(const String p_paths); + void parse_paths(const String p_paths); // Parse a comma separated string of io paths. + + bool has_path(const String p_path) const; // Has this io path + void add_path(const String p_path); // Add an io path + void remove_path(const String p_path); // Remove an io path + + // TODO add validation that we can display in the interface that checks if no two paths belong to the same top level path ~OpenXRIPBinding(); }; @@ -70,20 +78,24 @@ protected: static void _bind_methods(); public: - static Ref<OpenXRInteractionProfile> new_profile(const char *p_input_profile_path); + static Ref<OpenXRInteractionProfile> new_profile(const char *p_input_profile_path); // Helper function to create a new interaction profile - void set_interaction_profile_path(const String p_input_profile_path); - String get_interaction_profile_path() const; + void set_interaction_profile_path(const String p_input_profile_path); // Set our input profile path + String get_interaction_profile_path() const; // get our input profile path - void set_bindings(Array p_bindings); - Array get_bindings() const; + int get_binding_count() const; // Retrieve the number of bindings in this profile path + Ref<OpenXRIPBinding> get_binding(int p_index) const; + void set_bindings(Array p_bindings); // Set the bindings (for loading from a resource) + Array get_bindings() const; // Get the bindings (for saving to a resource) - void add_binding(Ref<OpenXRIPBinding> p_binding); - void remove_binding(Ref<OpenXRIPBinding> p_binding); + Ref<OpenXRIPBinding> get_binding_for_action(const Ref<OpenXRAction> p_action) const; // Get our binding record for a given action + void add_binding(Ref<OpenXRIPBinding> p_binding); // Add a binding object + void remove_binding(Ref<OpenXRIPBinding> p_binding); // Remove a binding object - void add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); + void add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); // Create a new binding for this profile + void remove_binding_for_action(const Ref<OpenXRAction> p_action); // Remove all bindings for this action ~OpenXRInteractionProfile(); }; -#endif // !OPENXR_INTERACTION_PROFILE_H +#endif // OPENXR_INTERACTION_PROFILE_H diff --git a/modules/openxr/doc_classes/OpenXRAction.xml b/modules/openxr/doc_classes/OpenXRAction.xml index 6ff8c1ad26..d1a2ce2d2e 100644 --- a/modules/openxr/doc_classes/OpenXRAction.xml +++ b/modules/openxr/doc_classes/OpenXRAction.xml @@ -5,7 +5,7 @@ </brief_description> <description> This resource defines an OpenXR action. Actions can be used both for inputs (buttons/joystick/trigger/etc) and outputs (haptics). - OpenXR performs automatic conversion between action type and input type whenever possible. An analogue trigger bound to a boolean action will thus return [code]false[/core] if the trigger is depressed and [code]true[/code] if pressed fully. + OpenXR performs automatic conversion between action type and input type whenever possible. An analogue trigger bound to a boolean action will thus return [code]false[/code] if the trigger is depressed and [code]true[/code] if pressed fully. Actions are not directly bound to specific devices, instead OpenXR recognises a limited number of top level paths that identify devices by usage. We can restrict which devices an action can be bound to by these top level paths. For instance an action that should only be used for hand held controllers can have the top level paths "/user/hand/left" and "/user/hand/right" associated with them. See the [url=https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-reserved]reserved path section in the OpenXR specification[/url] for more info on the top level paths. Note that the name of the resource is used to register the action with. </description> diff --git a/modules/openxr/doc_classes/OpenXRActionMap.xml b/modules/openxr/doc_classes/OpenXRActionMap.xml index f1def8aad8..8a2f666e3f 100644 --- a/modules/openxr/doc_classes/OpenXRActionMap.xml +++ b/modules/openxr/doc_classes/OpenXRActionMap.xml @@ -6,21 +6,21 @@ <description> OpenXR uses an action system similar to Godots Input map system to bind inputs and outputs on various types of XR controllers to named actions. OpenXR specifies more detail on these inputs and outputs than Godot supports. Another important distinction is that OpenXR offers no control over these bindings. The bindings we register are suggestions, it is up to the XR runtime to offer users the ability to change these bindings. This allows the XR runtime to fill in the gaps if new hardware becomes available. - The action map therefor needs to be loaded at startup and can't be changed afterwards. This resource is a container for the entire action map. + The action map therefore needs to be loaded at startup and can't be changed afterwards. This resource is a container for the entire action map. </description> <tutorials> </tutorials> <methods> <method name="add_action_set"> <return type="void" /> - <argument index="0" name="action_set" type="OpenXRActionSet" /> + <param index="0" name="action_set" type="OpenXRActionSet" /> <description> Add an action set. </description> </method> <method name="add_interaction_profile"> <return type="void" /> - <argument index="0" name="interaction_profile" type="OpenXRInteractionProfile" /> + <param index="0" name="interaction_profile" type="OpenXRInteractionProfile" /> <description> Add an interaction profile. </description> @@ -31,16 +31,56 @@ Setup this action set with our default actions. </description> </method> + <method name="find_action_set" qualifiers="const"> + <return type="OpenXRActionSet" /> + <param index="0" name="name" type="String" /> + <description> + Retrieve an action set by name. + </description> + </method> + <method name="find_interaction_profile" qualifiers="const"> + <return type="OpenXRInteractionProfile" /> + <param index="0" name="name" type="String" /> + <description> + Find an interaction profile by its name (path). + </description> + </method> + <method name="get_action_set" qualifiers="const"> + <return type="OpenXRActionSet" /> + <param index="0" name="idx" type="int" /> + <description> + Retrieve the action set at this index. + </description> + </method> + <method name="get_action_set_count" qualifiers="const"> + <return type="int" /> + <description> + Retrieve the number of actions sets in our action map. + </description> + </method> + <method name="get_interaction_profile" qualifiers="const"> + <return type="OpenXRInteractionProfile" /> + <param index="0" name="idx" type="int" /> + <description> + Get the interaction profile at this index. + </description> + </method> + <method name="get_interaction_profile_count" qualifiers="const"> + <return type="int" /> + <description> + Retrieve the number of interaction profiles in our action map. + </description> + </method> <method name="remove_action_set"> <return type="void" /> - <argument index="0" name="action_set" type="OpenXRActionSet" /> + <param index="0" name="action_set" type="OpenXRActionSet" /> <description> Remove an action set. </description> </method> <method name="remove_interaction_profile"> <return type="void" /> - <argument index="0" name="interaction_profile" type="OpenXRInteractionProfile" /> + <param index="0" name="interaction_profile" type="OpenXRInteractionProfile" /> <description> Remove an interaction profile. </description> @@ -48,8 +88,10 @@ </methods> <members> <member name="action_sets" type="Array" setter="set_action_sets" getter="get_action_sets" default="[]"> + Collection of [OpenXRActionSet]s that are part of this action map. </member> <member name="interaction_profiles" type="Array" setter="set_interaction_profiles" getter="get_interaction_profiles" default="[]"> + Collection of [OpenXRInteractionProfile]s that are part of this action map. </member> </members> </class> diff --git a/modules/openxr/doc_classes/OpenXRActionSet.xml b/modules/openxr/doc_classes/OpenXRActionSet.xml index 5a87de463e..db3259ec07 100644 --- a/modules/openxr/doc_classes/OpenXRActionSet.xml +++ b/modules/openxr/doc_classes/OpenXRActionSet.xml @@ -5,22 +5,27 @@ </brief_description> <description> Action sets in OpenXR define a collection of actions that can be activated in unison. This allows games to easily change between different states that require different inputs or need to reinterpret inputs. For instance we could have an action set that is active when a menu is open, an action set that is active when the player is freely walking around and an action set that is active when the player is controlling a vehicle. - Action sets can contain the same actions, or actions with the same name, if such action sets are active at the same time the action set with the highest priority defines which binding is active. - Note that the name of the resource is used to identify the action set within OpenXR. + Action sets can contain the same action with the same name, if such action sets are active at the same time the action set with the highest priority defines which binding is active. </description> <tutorials> </tutorials> <methods> <method name="add_action"> <return type="void" /> - <argument index="0" name="action" type="OpenXRAction" /> + <param index="0" name="action" type="OpenXRAction" /> <description> Add an action to this action set. </description> </method> + <method name="get_action_count" qualifiers="const"> + <return type="int" /> + <description> + Retrieve the number of actions in our action set. + </description> + </method> <method name="remove_action"> <return type="void" /> - <argument index="0" name="action" type="OpenXRAction" /> + <param index="0" name="action" type="OpenXRAction" /> <description> Remove an action from this action set. </description> diff --git a/modules/openxr/doc_classes/OpenXRIPBinding.xml b/modules/openxr/doc_classes/OpenXRIPBinding.xml index 3fdcde5eb5..00806bda06 100644 --- a/modules/openxr/doc_classes/OpenXRIPBinding.xml +++ b/modules/openxr/doc_classes/OpenXRIPBinding.xml @@ -4,13 +4,42 @@ Defines a binding between an [OpenXRAction] and an XR input or output. </brief_description> <description> - This binding resource binds an OpenXR action to inputs or outputs. As most controllers have left hand and right versions that are handled by the same interaction profile we can specify multiple bindings. For instance an action "Fire" could be bound to both "/user/hand/left/input/trigger" and "/user/hand/right/input/trigger". + This binding resource binds an [OpenXRAction] to inputs or outputs. As most controllers have left hand and right versions that are handled by the same interaction profile we can specify multiple bindings. For instance an action "Fire" could be bound to both "/user/hand/left/input/trigger" and "/user/hand/right/input/trigger". </description> <tutorials> </tutorials> + <methods> + <method name="add_path"> + <return type="void" /> + <param index="0" name="path" type="String" /> + <description> + Add an input/output path to this binding. + </description> + </method> + <method name="get_path_count" qualifiers="const"> + <return type="int" /> + <description> + Get the number of input/output paths in this binding. + </description> + </method> + <method name="has_path" qualifiers="const"> + <return type="bool" /> + <param index="0" name="path" type="String" /> + <description> + Returns [code]true[/code] if this input/output path is part of this binding. + </description> + </method> + <method name="remove_path"> + <return type="void" /> + <param index="0" name="path" type="String" /> + <description> + Removes this input/output path from this binding. + </description> + </method> + </methods> <members> <member name="action" type="OpenXRAction" setter="set_action" getter="get_action"> - Action that is bound to these paths. + [OpenXRAction] that is bound to these paths. </member> <member name="paths" type="PackedStringArray" setter="set_paths" getter="get_paths" default="PackedStringArray()"> Paths that define the inputs or outputs bound on the device. diff --git a/modules/openxr/doc_classes/OpenXRInteractionProfile.xml b/modules/openxr/doc_classes/OpenXRInteractionProfile.xml index a8629caae4..950bde031f 100644 --- a/modules/openxr/doc_classes/OpenXRInteractionProfile.xml +++ b/modules/openxr/doc_classes/OpenXRInteractionProfile.xml @@ -9,6 +9,21 @@ </description> <tutorials> </tutorials> + <methods> + <method name="get_binding" qualifiers="const"> + <return type="OpenXRIPBinding" /> + <param index="0" name="index" type="int" /> + <description> + Retrieve the binding at this index. + </description> + </method> + <method name="get_binding_count" qualifiers="const"> + <return type="int" /> + <description> + Get the number of bindings in this interaction profile. + </description> + </method> + </methods> <members> <member name="bindings" type="Array" setter="set_bindings" getter="get_bindings" default="[]"> Action bindings for this interaction profile. diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 1160061e04..25bf496de9 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -5,9 +5,36 @@ </brief_description> <description> The OpenXR interface allows Godot to interact with OpenXR runtimes and make it possible to create XR experiences and games. - Due to the needs of OpenXR this interface works slightly different then other plugin based XR interfaces. It needs to be initialised when Godot starts. You need to enable OpenXR, settings for this can be found in your games project settings under the XR heading. You do need to mark a viewport for use with XR in order for Godot to know which render result should be output to the headset. + Due to the needs of OpenXR this interface works slightly different than other plugin based XR interfaces. It needs to be initialised when Godot starts. You need to enable OpenXR, settings for this can be found in your games project settings under the XR heading. You do need to mark a viewport for use with XR in order for Godot to know which render result should be output to the headset. </description> <tutorials> - <link title="OpenXR documentation">$DOCS_URL/tutorials/vr/openxr/index.html</link> + <link title="Setting up XR">$DOCS_URL/tutorials/xr/setting_up_xr.html</link> </tutorials> + <signals> + <signal name="pose_recentered"> + <description> + Informs the user queued a recenter of the player position. + </description> + </signal> + <signal name="session_begun"> + <description> + Informs our OpenXR session has been started. + </description> + </signal> + <signal name="session_focussed"> + <description> + Informs our OpenXR session now has focus. + </description> + </signal> + <signal name="session_stopping"> + <description> + Informs our OpenXR session is stopping. + </description> + </signal> + <signal name="session_visible"> + <description> + Informs our OpenXR session is now visible (output is being sent to the HMD). + </description> + </signal> + </signals> </class> diff --git a/modules/openxr/editor/SCsub b/modules/openxr/editor/SCsub new file mode 100644 index 0000000000..ccf67a80d0 --- /dev/null +++ b/modules/openxr/editor/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/openxr/editor/openxr_action_editor.cpp b/modules/openxr/editor/openxr_action_editor.cpp new file mode 100644 index 0000000000..41c6465f43 --- /dev/null +++ b/modules/openxr/editor/openxr_action_editor.cpp @@ -0,0 +1,111 @@ +/*************************************************************************/ +/* openxr_action_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_action_editor.h" + +void OpenXRActionEditor::_bind_methods() { + ADD_SIGNAL(MethodInfo("remove", PropertyInfo(Variant::OBJECT, "action_editor"))); +} + +void OpenXRActionEditor::_theme_changed() { + rem_action->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); +} + +void OpenXRActionEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + _theme_changed(); + } break; + } +} + +void OpenXRActionEditor::_on_action_name_changed(const String p_new_text) { + // TODO validate if entry is allowed + + // If our localized name matches our action name, set this too + if (action->get_name() == action->get_localized_name()) { + action->set_localized_name(p_new_text); + action_localized_name->set_text(p_new_text); + } + action->set_name(p_new_text); +} + +void OpenXRActionEditor::_on_action_localized_name_changed(const String p_new_text) { + action->set_localized_name(p_new_text); +} + +void OpenXRActionEditor::_on_item_selected(int p_idx) { + ERR_FAIL_INDEX(p_idx, OpenXRAction::OPENXR_ACTION_MAX); + + action->set_action_type(OpenXRAction::ActionType(p_idx)); +} + +void OpenXRActionEditor::_on_remove_action() { + emit_signal("remove", this); +} + +OpenXRActionEditor::OpenXRActionEditor(Ref<OpenXRAction> p_action) { + action = p_action; + + set_h_size_flags(Control::SIZE_EXPAND_FILL); + + action_name = memnew(LineEdit); + action_name->set_text(action->get_name()); + action_name->set_custom_minimum_size(Size2(150.0, 0.0)); + action_name->connect("text_changed", callable_mp(this, &OpenXRActionEditor::_on_action_name_changed)); + add_child(action_name); + + action_localized_name = memnew(LineEdit); + action_localized_name->set_text(action->get_localized_name()); + action_localized_name->set_custom_minimum_size(Size2(150.0, 0.0)); + action_localized_name->set_h_size_flags(Control::SIZE_EXPAND_FILL); + action_localized_name->connect("text_changed", callable_mp(this, &OpenXRActionEditor::_on_action_localized_name_changed)); + add_child(action_localized_name); + + action_type = memnew(OptionButton); + action_type->add_item("Bool", OpenXRAction::OPENXR_ACTION_BOOL); + action_type->add_item("Float", OpenXRAction::OPENXR_ACTION_FLOAT); + action_type->add_item("Vector2", OpenXRAction::OPENXR_ACTION_VECTOR2); + action_type->add_item("Pose", OpenXRAction::OPENXR_ACTION_POSE); + action_type->add_item("Haptic", OpenXRAction::OPENXR_ACTION_HAPTIC); + action_type->select(int(action->get_action_type())); + action_type->set_custom_minimum_size(Size2(100.0, 0.0)); + action_type->connect("item_selected", callable_mp(this, &OpenXRActionEditor::_on_item_selected)); + add_child(action_type); + + // maybe add dropdown to edit our toplevel paths, or do we deduce them from our suggested bindings? + + rem_action = memnew(Button); + rem_action->set_tooltip(TTR("Remove action")); + rem_action->connect("pressed", callable_mp(this, &OpenXRActionEditor::_on_remove_action)); + rem_action->set_flat(true); + add_child(rem_action); +} diff --git a/modules/openxr/editor/openxr_action_editor.h b/modules/openxr/editor/openxr_action_editor.h new file mode 100644 index 0000000000..6cf098cf08 --- /dev/null +++ b/modules/openxr/editor/openxr_action_editor.h @@ -0,0 +1,67 @@ +/*************************************************************************/ +/* openxr_action_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_ACTION_EDITOR_H +#define OPENXR_ACTION_EDITOR_H + +#include "../action_map/openxr_action.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/option_button.h" +#include "scene/gui/text_edit.h" + +class OpenXRActionEditor : public HBoxContainer { + GDCLASS(OpenXRActionEditor, HBoxContainer); + +private: + Ref<OpenXRAction> action; + + LineEdit *action_name = nullptr; + LineEdit *action_localized_name = nullptr; + OptionButton *action_type = nullptr; + Button *rem_action = nullptr; + + void _theme_changed(); + void _on_action_name_changed(const String p_new_text); + void _on_action_localized_name_changed(const String p_new_text); + void _on_item_selected(int p_idx); + void _on_remove_action(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + Ref<OpenXRAction> get_action() { return action; }; + OpenXRActionEditor(Ref<OpenXRAction> p_action); +}; + +#endif // OPENXR_ACTION_EDITOR_H diff --git a/modules/openxr/editor/openxr_action_map_editor.cpp b/modules/openxr/editor/openxr_action_map_editor.cpp new file mode 100644 index 0000000000..0a2d0a3110 --- /dev/null +++ b/modules/openxr/editor/openxr_action_map_editor.cpp @@ -0,0 +1,371 @@ +/*************************************************************************/ +/* openxr_action_map_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_action_map_editor.h" + +#include "core/config/project_settings.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" + +// TODO implement redo/undo system + +void OpenXRActionMapEditor::_bind_methods() { + ClassDB::bind_method("_add_action_set_editor", &OpenXRActionMapEditor::_add_action_set_editor); + ClassDB::bind_method("_update_action_sets", &OpenXRActionMapEditor::_update_action_sets); + + ClassDB::bind_method("_add_interaction_profile_editor", &OpenXRActionMapEditor::_add_interaction_profile_editor); + ClassDB::bind_method("_update_interaction_profiles", &OpenXRActionMapEditor::_update_interaction_profiles); + + ClassDB::bind_method(D_METHOD("_add_action_set", "name"), &OpenXRActionMapEditor::_add_action_set); + ClassDB::bind_method(D_METHOD("_set_focus_on_action_set", "action_set"), &OpenXRActionMapEditor::_set_focus_on_action_set); + ClassDB::bind_method(D_METHOD("_remove_action_set", "name"), &OpenXRActionMapEditor::_remove_action_set); +} + +void OpenXRActionMapEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + for (int i = 0; i < tabs->get_child_count(); i++) { + Control *tab = static_cast<Control *>(tabs->get_child(i)); + if (tab) { + tab->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + } + } + } break; + + case NOTIFICATION_READY: { + _update_action_sets(); + _update_interaction_profiles(); + } break; + } +} + +OpenXRActionSetEditor *OpenXRActionMapEditor::_add_action_set_editor(Ref<OpenXRActionSet> p_action_set) { + ERR_FAIL_COND_V(p_action_set.is_null(), nullptr); + + OpenXRActionSetEditor *action_set_editor = memnew(OpenXRActionSetEditor(action_map, p_action_set)); + action_set_editor->connect("remove", callable_mp(this, &OpenXRActionMapEditor::_on_remove_action_set)); + action_set_editor->connect("action_removed", callable_mp(this, &OpenXRActionMapEditor::_on_action_removed)); + actionsets_vb->add_child(action_set_editor); + + return action_set_editor; +} + +void OpenXRActionMapEditor::_update_action_sets() { + // out with the old... + while (actionsets_vb->get_child_count() > 0) { + memdelete(actionsets_vb->get_child(0)); + } + + // in with the new... + if (action_map.is_valid()) { + Array action_sets = action_map->get_action_sets(); + for (int i = 0; i < action_sets.size(); i++) { + Ref<OpenXRActionSet> action_set = action_sets[i]; + _add_action_set_editor(action_set); + } + } +} + +OpenXRInteractionProfileEditorBase *OpenXRActionMapEditor::_add_interaction_profile_editor(Ref<OpenXRInteractionProfile> p_interaction_profile) { + ERR_FAIL_COND_V(p_interaction_profile.is_null(), nullptr); + + String profile_path = p_interaction_profile->get_interaction_profile_path(); + + // 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 { + // instance generic editor + new_profile_editor = memnew(OpenXRInteractionProfileEditor(action_map, p_interaction_profile)); + } + + // now add it in.. + ERR_FAIL_NULL_V(new_profile_editor, nullptr); + tabs->add_child(new_profile_editor); + new_profile_editor->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + tabs->set_tab_button_icon(tabs->get_tab_count() - 1, get_theme_icon(SNAME("close"), SNAME("TabBar"))); + + interaction_profiles.push_back(new_profile_editor); + + return new_profile_editor; +} + +void OpenXRActionMapEditor::_update_interaction_profiles() { + // out with the old... + while (interaction_profiles.size() > 0) { + Node *interaction_profile = interaction_profiles[0]; + interaction_profiles.remove_at(0); + + tabs->remove_child(interaction_profile); + interaction_profile->queue_delete(); + } + + // in with the new... + if (action_map.is_valid()) { + Array new_interaction_profiles = action_map->get_interaction_profiles(); + for (int i = 0; i < new_interaction_profiles.size(); i++) { + Ref<OpenXRInteractionProfile> interaction_profile = new_interaction_profiles[i]; + _add_interaction_profile_editor(interaction_profile); + } + } +} + +OpenXRActionSetEditor *OpenXRActionMapEditor::_add_action_set(String p_name) { + ERR_FAIL_COND_V(action_map.is_null(), nullptr); + Ref<OpenXRActionSet> new_action_set; + + // add our new action set + new_action_set.instantiate(); + new_action_set->set_name(p_name); + new_action_set->set_localized_name(p_name); + action_map->add_action_set(new_action_set); + + // update our editor right away + return _add_action_set_editor(new_action_set); +} + +void OpenXRActionMapEditor::_remove_action_set(String p_name) { + ERR_FAIL_COND(action_map.is_null()); + Ref<OpenXRActionSet> action_set = action_map->find_action_set(p_name); + ERR_FAIL_COND(action_set.is_null()); + + if (action_set->get_action_count() > 0) { + // we should remove these and add to our redo/undo step before calling _remove_action_set + WARN_PRINT("Action set still has associated actions before being removed!"); + } + + // now we remove it + action_map->remove_action_set(action_set); +} + +void OpenXRActionMapEditor::_on_add_action_set() { + ERR_FAIL_COND(action_map.is_null()); + String new_name = "New"; + int count = 0; + + while (action_map->find_action_set(new_name).is_valid()) { + new_name = "New_" + itos(count++); + } + + OpenXRActionSetEditor *new_action_set_editor = _add_action_set(new_name); + + // Make sure our action set is the current tab + tabs->set_current_tab(0); + + call_deferred("_set_focus_on_action_set", new_action_set_editor); +} + +void OpenXRActionMapEditor::_set_focus_on_action_set(OpenXRActionSetEditor *p_action_set_editor) { + // Scroll down to our new entry + actionsets_scroll->ensure_control_visible(p_action_set_editor); + + // Set focus on this entry + p_action_set_editor->set_focus_on_entry(); +} + +void OpenXRActionMapEditor::_on_remove_action_set(Object *p_action_set_editor) { + ERR_FAIL_COND(action_map.is_null()); + + OpenXRActionSetEditor *action_set_editor = Object::cast_to<OpenXRActionSetEditor>(p_action_set_editor); + ERR_FAIL_NULL(action_set_editor); + ERR_FAIL_COND(action_set_editor->get_parent() != actionsets_vb); + Ref<OpenXRActionSet> action_set = action_set_editor->get_action_set(); + ERR_FAIL_COND(action_set.is_null()); + + action_map->remove_action_set(action_set); + actionsets_vb->remove_child(action_set_editor); + action_set_editor->queue_delete(); +} + +void OpenXRActionMapEditor::_on_action_removed() { + // make sure our interaction profiles are updated + _update_interaction_profiles(); +} + +void OpenXRActionMapEditor::_on_add_interaction_profile() { + ERR_FAIL_COND(action_map.is_null()); + + PackedStringArray already_selected; + + for (int i = 0; i < action_map->get_interaction_profile_count(); i++) { + already_selected.push_back(action_map->get_interaction_profile(i)->get_interaction_profile_path()); + } + + select_interaction_profile_dialog->open(already_selected); +} + +void OpenXRActionMapEditor::_on_interaction_profile_selected(const String p_path) { + ERR_FAIL_COND(action_map.is_null()); + + Ref<OpenXRInteractionProfile> new_profile; + new_profile.instantiate(); + new_profile->set_interaction_profile_path(p_path); + action_map->add_interaction_profile(new_profile); + + _add_interaction_profile_editor(new_profile); + + tabs->set_current_tab(tabs->get_tab_count() - 1); +} + +void OpenXRActionMapEditor::_load_action_map(const String p_path, bool p_create_new_if_missing) { + action_map = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE); + if (action_map.is_null()) { + if (p_create_new_if_missing) { + action_map.instantiate(); + action_map->create_default_action_sets(); + } else { + EditorNode::get_singleton()->show_warning(TTR("Invalid file, not an OpenXR action map.")); + + edited_path = ""; + header_label->set_text(""); + return; + } + } + + edited_path = p_path; + header_label->set_text(TTR("OpenXR Action map:") + " " + p_path.get_file()); +} + +void OpenXRActionMapEditor::_on_save_action_map() { + Error err = ResourceSaver::save(action_map, edited_path); + if (err != OK) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file: %s"), edited_path)); + return; + } + + _update_action_sets(); + _update_interaction_profiles(); +} + +void OpenXRActionMapEditor::_on_reset_to_default_layout() { + // create a new one + action_map.unref(); + action_map.instantiate(); + action_map->create_default_action_sets(); + + _update_action_sets(); + _update_interaction_profiles(); +} + +void OpenXRActionMapEditor::_on_tabs_tab_changed(int p_tab) { +} + +void OpenXRActionMapEditor::_on_tab_button_pressed(int p_tab) { + OpenXRInteractionProfileEditorBase *profile_editor = static_cast<OpenXRInteractionProfileEditorBase *>(tabs->get_tab_control(p_tab)); + ERR_FAIL_NULL(profile_editor); + + Ref<OpenXRInteractionProfile> interaction_profile = profile_editor->get_interaction_profile(); + ERR_FAIL_COND(interaction_profile.is_null()); + + action_map->remove_interaction_profile(interaction_profile); + tabs->remove_child(profile_editor); + profile_editor->queue_delete(); +} + +void OpenXRActionMapEditor::open_action_map(String p_path) { + EditorNode::get_singleton()->make_bottom_panel_item_visible(this); + + _load_action_map(p_path); + + _update_action_sets(); + _update_interaction_profiles(); +} + +OpenXRActionMapEditor::OpenXRActionMapEditor() { + set_custom_minimum_size(Size2(0.0, 300.0)); + + top_hb = memnew(HBoxContainer); + add_child(top_hb); + + header_label = memnew(Label); + header_label->set_text(String(TTR("Action Map"))); + header_label->set_clip_text(true); + header_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + top_hb->add_child(header_label); + + add_action_set = memnew(Button); + add_action_set->set_text(TTR("Add Action Set")); + add_action_set->set_tooltip(TTR("Add an action set.")); + add_action_set->connect("pressed", callable_mp(this, &OpenXRActionMapEditor::_on_add_action_set)); + top_hb->add_child(add_action_set); + + add_interaction_profile = memnew(Button); + add_interaction_profile->set_text(TTR("Add profile")); + add_interaction_profile->set_tooltip(TTR("Add an interaction profile.")); + add_interaction_profile->connect("pressed", callable_mp(this, &OpenXRActionMapEditor::_on_add_interaction_profile)); + top_hb->add_child(add_interaction_profile); + + VSeparator *vseparator = memnew(VSeparator); + top_hb->add_child(vseparator); + + save_as = memnew(Button); + save_as->set_text(TTR("Save")); + save_as->set_tooltip(TTR("Save this OpenXR action map.")); + save_as->connect("pressed", callable_mp(this, &OpenXRActionMapEditor::_on_save_action_map)); + top_hb->add_child(save_as); + + _default = memnew(Button); + _default->set_text(TTR("Reset to Default")); + _default->set_tooltip(TTR("Reset to default OpenXR action map.")); + _default->connect("pressed", callable_mp(this, &OpenXRActionMapEditor::_on_reset_to_default_layout)); + top_hb->add_child(_default); + + tabs = memnew(TabContainer); + tabs->set_h_size_flags(SIZE_EXPAND_FILL); + tabs->set_v_size_flags(SIZE_EXPAND_FILL); + tabs->set_theme_type_variation("TabContainerOdd"); + tabs->connect("tab_changed", callable_mp(this, &OpenXRActionMapEditor::_on_tabs_tab_changed)); + tabs->connect("tab_button_pressed", callable_mp(this, &OpenXRActionMapEditor::_on_tab_button_pressed)); + add_child(tabs); + + actionsets_scroll = memnew(ScrollContainer); + actionsets_scroll->set_h_size_flags(SIZE_EXPAND_FILL); + actionsets_scroll->set_v_size_flags(SIZE_EXPAND_FILL); + actionsets_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + tabs->add_child(actionsets_scroll); + actionsets_scroll->set_name(TTR("Action Sets")); + + actionsets_vb = memnew(VBoxContainer); + actionsets_vb->set_h_size_flags(SIZE_EXPAND_FILL); + actionsets_scroll->add_child(actionsets_vb); + + select_interaction_profile_dialog = memnew(OpenXRSelectInteractionProfileDialog); + select_interaction_profile_dialog->connect("interaction_profile_selected", callable_mp(this, &OpenXRActionMapEditor::_on_interaction_profile_selected)); + add_child(select_interaction_profile_dialog); + + _load_action_map(ProjectSettings::get_singleton()->get("xr/openxr/default_action_map")); +} + +OpenXRActionMapEditor::~OpenXRActionMapEditor() { +} diff --git a/modules/openxr/editor/openxr_action_map_editor.h b/modules/openxr/editor/openxr_action_map_editor.h new file mode 100644 index 0000000000..a19bc90f56 --- /dev/null +++ b/modules/openxr/editor/openxr_action_map_editor.h @@ -0,0 +1,100 @@ +/*************************************************************************/ +/* openxr_action_map_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_ACTION_MAP_EDITOR_H +#define OPENXR_ACTION_MAP_EDITOR_H + +#include "../action_map/openxr_action_map.h" +#include "../editor/openxr_action_set_editor.h" +#include "../editor/openxr_interaction_profile_editor.h" +#include "../editor/openxr_select_interaction_profile_dialog.h" + +#include "editor/editor_plugin.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/label.h" +#include "scene/gui/scroll_container.h" +#include "scene/gui/tab_container.h" + +class OpenXRActionMapEditor : public VBoxContainer { + GDCLASS(OpenXRActionMapEditor, VBoxContainer); + +private: + String edited_path; + Ref<OpenXRActionMap> action_map; + Vector<Node *> interaction_profiles; + + HBoxContainer *top_hb = nullptr; + Label *header_label = nullptr; + Button *add_action_set = nullptr; + Button *add_interaction_profile = nullptr; + Button *load = nullptr; + Button *save_as = nullptr; + Button *_default = nullptr; + TabContainer *tabs = nullptr; + ScrollContainer *actionsets_scroll = nullptr; + VBoxContainer *actionsets_vb = nullptr; + OpenXRSelectInteractionProfileDialog *select_interaction_profile_dialog = nullptr; + + OpenXRActionSetEditor *_add_action_set_editor(Ref<OpenXRActionSet> p_action_set); + void _update_action_sets(); + OpenXRInteractionProfileEditorBase *_add_interaction_profile_editor(Ref<OpenXRInteractionProfile> p_interaction_profile); + void _update_interaction_profiles(); + + OpenXRActionSetEditor *_add_action_set(String p_name); + void _remove_action_set(String p_name); + + void _on_add_action_set(); + void _set_focus_on_action_set(OpenXRActionSetEditor *p_action_set_editor); + void _on_remove_action_set(Object *p_action_set_editor); + void _on_action_removed(); + + void _on_add_interaction_profile(); + void _on_interaction_profile_selected(const String p_path); + + void _load_action_map(const String p_path, bool p_create_new_if_missing = false); + void _on_save_action_map(); + void _on_reset_to_default_layout(); + + void _on_tabs_tab_changed(int p_tab); + void _on_tab_button_pressed(int p_tab); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + void open_action_map(String p_path); + + OpenXRActionMapEditor(); + ~OpenXRActionMapEditor(); +}; + +#endif // OPENXR_ACTION_MAP_EDITOR_H diff --git a/modules/openxr/editor/openxr_action_set_editor.cpp b/modules/openxr/editor/openxr_action_set_editor.cpp new file mode 100644 index 0000000000..7bf8557c5b --- /dev/null +++ b/modules/openxr/editor/openxr_action_set_editor.cpp @@ -0,0 +1,218 @@ +/*************************************************************************/ +/* openxr_action_set_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_action_set_editor.h" +#include "openxr_action_editor.h" + +void OpenXRActionSetEditor::_bind_methods() { + ADD_SIGNAL(MethodInfo("remove", PropertyInfo(Variant::OBJECT, "action_set_editor"))); + ADD_SIGNAL(MethodInfo("action_removed")); +} + +void OpenXRActionSetEditor::_set_fold_icon() { + if (is_expanded) { + fold_btn->set_icon(get_theme_icon(SNAME("GuiTreeArrowDown"), SNAME("EditorIcons"))); + } else { + fold_btn->set_icon(get_theme_icon(SNAME("GuiTreeArrowRight"), SNAME("EditorIcons"))); + } +} + +void OpenXRActionSetEditor::_theme_changed() { + _set_fold_icon(); + add_action->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + rem_action_set->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); +} + +void OpenXRActionSetEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + _theme_changed(); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainer"))); + } break; + } +} + +OpenXRActionEditor *OpenXRActionSetEditor::_add_action_editor(Ref<OpenXRAction> p_action) { + OpenXRActionEditor *action_editor = memnew(OpenXRActionEditor(p_action)); + action_editor->connect("remove", callable_mp(this, &OpenXRActionSetEditor::_on_remove_action)); + actions_vb->add_child(action_editor); + + return action_editor; +} + +void OpenXRActionSetEditor::_update_actions() { + // out with the old... + while (actions_vb->get_child_count() > 0) { + memdelete(actions_vb->get_child(0)); + } + + // in with the new... + Array actions = action_set->get_actions(); + for (int i = 0; i < actions.size(); i++) { + Ref<OpenXRAction> action = actions[i]; + _add_action_editor(action); + } +} + +void OpenXRActionSetEditor::_on_toggle_expand() { + is_expanded = !is_expanded; + actions_vb->set_visible(is_expanded); + _set_fold_icon(); +} + +void OpenXRActionSetEditor::_on_action_set_name_changed(const String p_new_text) { + // TODO validate if entry is allowed + + // If our localized name matches our action set name, set this too + if (action_set->get_name() == action_set->get_localized_name()) { + action_set->set_localized_name(p_new_text); + action_set_localized_name->set_text(p_new_text); + } + action_set->set_name(p_new_text); +} + +void OpenXRActionSetEditor::_on_action_set_localized_name_changed(const String p_new_text) { + action_set->set_localized_name(p_new_text); +} + +void OpenXRActionSetEditor::_on_action_set_priority_changed(const String p_new_text) { + int64_t value = p_new_text.to_int(); + + action_set->set_priority(value); +} + +void OpenXRActionSetEditor::_on_add_action() { + Ref<OpenXRAction> new_action; + + new_action.instantiate(); + new_action->set_name("New"); + new_action->set_localized_name("New"); + action_set->add_action(new_action); + + _add_action_editor(new_action); + + // TODO handle focus +} + +void OpenXRActionSetEditor::_on_remove_action_set() { + emit_signal("remove", this); +} + +void OpenXRActionSetEditor::_on_remove_action(Object *p_action_editor) { + OpenXRActionEditor *action_editor = Object::cast_to<OpenXRActionEditor>(p_action_editor); + ERR_FAIL_NULL(action_editor); + ERR_FAIL_COND(action_editor->get_parent() != actions_vb); + Ref<OpenXRAction> action = action_editor->get_action(); + ERR_FAIL_COND(action.is_null()); + + // TODO add undo/redo action + + // TODO find where this action is used by our interaction profiles and remove it there + + // And remove it.... + action_map->remove_action(action->get_name_with_set()); // remove it from the set and any interaction profile it relates to + actions_vb->remove_child(action_editor); + action_editor->queue_delete(); + + // Let action map editor know so we can update our interaction profiles + emit_signal("action_removed"); +} + +void OpenXRActionSetEditor::set_focus_on_entry() { + ERR_FAIL_NULL(action_set_name); + action_set_name->grab_focus(); +} + +OpenXRActionSetEditor::OpenXRActionSetEditor(Ref<OpenXRActionMap> p_action_map, Ref<OpenXRActionSet> p_action_set) { + action_map = p_action_map; + action_set = p_action_set; + + set_h_size_flags(Control::SIZE_EXPAND_FILL); + + panel = memnew(PanelContainer); + panel->set_h_size_flags(Control::SIZE_EXPAND_FILL); + add_child(panel); + + HBoxContainer *panel_hb = memnew(HBoxContainer); + panel_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + panel->add_child(panel_hb); + + fold_btn = memnew(Button); + fold_btn->set_v_size_flags(Control::SIZE_SHRINK_BEGIN); + fold_btn->connect("pressed", callable_mp(this, &OpenXRActionSetEditor::_on_toggle_expand)); + fold_btn->set_flat(true); + panel_hb->add_child(fold_btn); + + main_vb = memnew(VBoxContainer); + main_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + panel_hb->add_child(main_vb); + + action_set_hb = memnew(HBoxContainer); + action_set_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + main_vb->add_child(action_set_hb); + + action_set_name = memnew(LineEdit); + action_set_name->set_text(action_set->get_name()); + action_set_name->set_custom_minimum_size(Size2(150.0, 0.0)); + action_set_name->connect("text_changed", callable_mp(this, &OpenXRActionSetEditor::_on_action_set_name_changed)); + action_set_hb->add_child(action_set_name); + + action_set_localized_name = memnew(LineEdit); + action_set_localized_name->set_text(action_set->get_localized_name()); + action_set_localized_name->set_custom_minimum_size(Size2(150.0, 0.0)); + action_set_localized_name->set_h_size_flags(Control::SIZE_EXPAND_FILL); + action_set_localized_name->connect("text_changed", callable_mp(this, &OpenXRActionSetEditor::_on_action_set_localized_name_changed)); + action_set_hb->add_child(action_set_localized_name); + + action_set_priority = memnew(TextEdit); + action_set_priority->set_text(itos(action_set->get_priority())); + action_set_priority->set_custom_minimum_size(Size2(50.0, 0.0)); + action_set_priority->connect("text_changed", callable_mp(this, &OpenXRActionSetEditor::_on_action_set_priority_changed)); + action_set_hb->add_child(action_set_priority); + + add_action = memnew(Button); + add_action->set_tooltip("Add Action."); + add_action->connect("pressed", callable_mp(this, &OpenXRActionSetEditor::_on_add_action)); + add_action->set_flat(true); + action_set_hb->add_child(add_action); + + rem_action_set = memnew(Button); + rem_action_set->set_tooltip("Remove Action Set."); + rem_action_set->connect("pressed", callable_mp(this, &OpenXRActionSetEditor::_on_remove_action_set)); + rem_action_set->set_flat(true); + action_set_hb->add_child(rem_action_set); + + actions_vb = memnew(VBoxContainer); + actions_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + main_vb->add_child(actions_vb); + + _update_actions(); +} diff --git a/modules/openxr/editor/openxr_action_set_editor.h b/modules/openxr/editor/openxr_action_set_editor.h new file mode 100644 index 0000000000..d8c85d03dd --- /dev/null +++ b/modules/openxr/editor/openxr_action_set_editor.h @@ -0,0 +1,88 @@ +/*************************************************************************/ +/* openxr_action_set_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_ACTION_SET_EDITOR_H +#define OPENXR_ACTION_SET_EDITOR_H + +#include "../action_map/openxr_action_map.h" +#include "../action_map/openxr_action_set.h" +#include "openxr_action_editor.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/text_edit.h" + +class OpenXRActionSetEditor : public HBoxContainer { + GDCLASS(OpenXRActionSetEditor, HBoxContainer); + +private: + Ref<OpenXRActionMap> action_map; + Ref<OpenXRActionSet> action_set; + + bool is_expanded = true; + + PanelContainer *panel = nullptr; + Button *fold_btn = nullptr; + VBoxContainer *main_vb = nullptr; + HBoxContainer *action_set_hb = nullptr; + LineEdit *action_set_name = nullptr; + LineEdit *action_set_localized_name = nullptr; + TextEdit *action_set_priority = nullptr; + Button *add_action = nullptr; + Button *rem_action_set = nullptr; + VBoxContainer *actions_vb = nullptr; + + void _set_fold_icon(); + void _theme_changed(); + OpenXRActionEditor *_add_action_editor(Ref<OpenXRAction> p_action); + void _update_actions(); + + void _on_toggle_expand(); + void _on_action_set_name_changed(const String p_new_text); + void _on_action_set_localized_name_changed(const String p_new_text); + void _on_action_set_priority_changed(const String p_new_text); + void _on_add_action(); + void _on_remove_action_set(); + + void _on_remove_action(Object *p_action_editor); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + Ref<OpenXRActionSet> get_action_set() { return action_set; }; + void set_focus_on_entry(); + + OpenXRActionSetEditor(Ref<OpenXRActionMap> p_action_map, Ref<OpenXRActionSet> p_action_set); +}; + +#endif // OPENXR_ACTION_SET_EDITOR_H diff --git a/modules/openxr/editor/openxr_editor_plugin.cpp b/modules/openxr/editor/openxr_editor_plugin.cpp new file mode 100644 index 0000000000..b87b538511 --- /dev/null +++ b/modules/openxr/editor/openxr_editor_plugin.cpp @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* openxr_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_editor_plugin.h" + +#include "../action_map/openxr_action_map.h" +#include "editor/editor_node.h" + +void OpenXREditorPlugin::edit(Object *p_node) { + if (Object::cast_to<OpenXRActionMap>(p_node)) { + String path = Object::cast_to<OpenXRActionMap>(p_node)->get_path(); + if (path.is_resource_file()) { + action_map_editor->open_action_map(path); + } + } +} + +bool OpenXREditorPlugin::handles(Object *p_node) const { + return (Object::cast_to<OpenXRActionMap>(p_node) != nullptr); +} + +void OpenXREditorPlugin::make_visible(bool p_visible) { +} + +OpenXREditorPlugin::OpenXREditorPlugin() { + action_map_editor = memnew(OpenXRActionMapEditor); + EditorNode::get_singleton()->add_bottom_panel_item(TTR("OpenXR Action Map"), action_map_editor); +} + +OpenXREditorPlugin::~OpenXREditorPlugin() { +} diff --git a/modules/openxr/editor/openxr_editor_plugin.h b/modules/openxr/editor/openxr_editor_plugin.h new file mode 100644 index 0000000000..ce230ee95b --- /dev/null +++ b/modules/openxr/editor/openxr_editor_plugin.h @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* openxr_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_EDITOR_PLUGIN_H +#define OPENXR_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" +#include "openxr_action_map_editor.h" + +class OpenXREditorPlugin : public EditorPlugin { + GDCLASS(OpenXREditorPlugin, EditorPlugin); + + OpenXRActionMapEditor *action_map_editor = nullptr; + +public: + virtual String get_name() const override { return "OpenXRPlugin"; } + bool has_main_screen() const override { return false; } + virtual void edit(Object *p_node) override; + virtual bool handles(Object *p_node) const override; + virtual void make_visible(bool p_visible) override; + + OpenXREditorPlugin(); + ~OpenXREditorPlugin(); +}; + +#endif // OPENXR_EDITOR_PLUGIN_H diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.cpp b/modules/openxr/editor/openxr_interaction_profile_editor.cpp new file mode 100644 index 0000000000..e2dc2d1b74 --- /dev/null +++ b/modules/openxr/editor/openxr_interaction_profile_editor.cpp @@ -0,0 +1,269 @@ +/*************************************************************************/ +/* openxr_interaction_profile_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_interaction_profile_editor.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/separator.h" +#include "scene/gui/text_edit.h" + +/////////////////////////////////////////////////////////////////////////// +// Interaction profile editor base + +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("_update_interaction_profile"), &OpenXRInteractionProfileEditorBase::_update_interaction_profile); +} + +void OpenXRInteractionProfileEditorBase::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + _update_interaction_profile(); + } break; + + case NOTIFICATION_THEME_CHANGED: { + _theme_changed(); + } break; + } +} + +void OpenXRInteractionProfileEditorBase::_add_binding(const String p_action, const String p_path) { + ERR_FAIL_COND(action_map.is_null()); + ERR_FAIL_COND(interaction_profile.is_null()); + + Ref<OpenXRAction> action = action_map->get_action(p_action); + ERR_FAIL_COND(action.is_null()); + + Ref<OpenXRIPBinding> binding = interaction_profile->get_binding_for_action(action); + if (binding.is_null()) { + // create a new binding + binding.instantiate(); + binding->set_action(action); + interaction_profile->add_binding(binding); + } + + binding->add_path(p_path); + + // Update our toplevel paths + action->set_toplevel_paths(action_map->get_top_level_paths(action)); + + call_deferred("_update_interaction_profile"); +} + +void OpenXRInteractionProfileEditorBase::_remove_binding(const String p_action, const String p_path) { + ERR_FAIL_COND(action_map.is_null()); + ERR_FAIL_COND(interaction_profile.is_null()); + + Ref<OpenXRAction> action = action_map->get_action(p_action); + ERR_FAIL_COND(action.is_null()); + + Ref<OpenXRIPBinding> binding = interaction_profile->get_binding_for_action(action); + if (binding.is_valid()) { + binding->remove_path(p_path); + + if (binding->get_path_count() == 0) { + interaction_profile->remove_binding(binding); + } + + // Update our toplevel paths + action->set_toplevel_paths(action_map->get_top_level_paths(action)); + + call_deferred("_update_interaction_profile"); + } +} + +OpenXRInteractionProfileEditorBase::OpenXRInteractionProfileEditorBase(Ref<OpenXRActionMap> p_action_map, Ref<OpenXRInteractionProfile> p_interaction_profile) { + action_map = p_action_map; + interaction_profile = p_interaction_profile; + String profile_path = interaction_profile->get_interaction_profile_path(); + String profile_name = profile_path; + + profile_def = OpenXRDefs::get_profile(profile_path); + if (profile_def != nullptr) { + profile_name = profile_def->display_name; + } + + set_name(profile_name); + set_h_size_flags(SIZE_EXPAND_FILL); + set_v_size_flags(SIZE_EXPAND_FILL); +} + +/////////////////////////////////////////////////////////////////////////// +// Default interaction profile editor + +void OpenXRInteractionProfileEditor::select_action_for(const String p_io_path) { + selecting_for_io_path = p_io_path; + select_action_dialog->open(); +} + +void OpenXRInteractionProfileEditor::action_selected(const String p_action) { + _add_binding(p_action, selecting_for_io_path); + selecting_for_io_path = ""; +} + +void OpenXRInteractionProfileEditor::_add_io_path(VBoxContainer *p_container, const OpenXRDefs::IOPath *p_io_path) { + HBoxContainer *path_hb = memnew(HBoxContainer); + path_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + p_container->add_child(path_hb); + + Label *path_label = memnew(Label); + path_label->set_text(p_io_path->display_name); + path_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + path_hb->add_child(path_label); + + Label *type_label = memnew(Label); + switch (p_io_path->action_type) { + case OpenXRAction::OPENXR_ACTION_BOOL: { + type_label->set_text(TTR("Boolean")); + } break; + case OpenXRAction::OPENXR_ACTION_FLOAT: { + type_label->set_text(TTR("Float")); + } break; + case OpenXRAction::OPENXR_ACTION_VECTOR2: { + type_label->set_text(TTR("Vector2")); + } break; + case OpenXRAction::OPENXR_ACTION_POSE: { + type_label->set_text(TTR("Pose")); + } break; + case OpenXRAction::OPENXR_ACTION_HAPTIC: { + type_label->set_text(TTR("Haptic")); + } break; + default: { + type_label->set_text(TTR("Unknown")); + } break; + } + type_label->set_custom_minimum_size(Size2(50.0, 0.0)); + path_hb->add_child(type_label); + + Button *path_add = memnew(Button); + path_add->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + path_add->set_flat(true); + path_add->connect("pressed", callable_mp(this, &OpenXRInteractionProfileEditor::select_action_for).bind(String(p_io_path->openxr_path))); + path_hb->add_child(path_add); + + if (interaction_profile.is_valid()) { + String io_path = String(p_io_path->openxr_path); + Array bindings = interaction_profile->get_bindings(); + for (int i = 0; i < bindings.size(); i++) { + Ref<OpenXRIPBinding> binding = bindings[i]; + if (binding->has_path(io_path)) { + Ref<OpenXRAction> action = binding->get_action(); + + HBoxContainer *action_hb = memnew(HBoxContainer); + action_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + p_container->add_child(action_hb); + + Control *indent_node = memnew(Control); + indent_node->set_custom_minimum_size(Size2(10.0, 0.0)); + action_hb->add_child(indent_node); + + Label *action_label = memnew(Label); + action_label->set_text(action->get_name_with_set() + ": " + action->get_localized_name()); + action_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + action_hb->add_child(action_label); + + Button *action_rem = memnew(Button); + action_rem->set_flat(true); + action_rem->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + action_rem->connect("pressed", callable_mp((OpenXRInteractionProfileEditorBase *)this, &OpenXRInteractionProfileEditorBase::_remove_binding).bind(action->get_name_with_set(), String(p_io_path->openxr_path))); + action_hb->add_child(action_rem); + } + } + } +} + +void OpenXRInteractionProfileEditor::_update_interaction_profile() { + ERR_FAIL_NULL(profile_def); + + // out with the old... + while (main_hb->get_child_count() > 0) { + memdelete(main_hb->get_child(0)); + } + + // in with the new... + + // Determine toplevel paths + Vector<const OpenXRDefs::TopLevelPath *> top_level_paths; + for (int i = 0; i < profile_def->io_path_count; i++) { + const OpenXRDefs::IOPath *io_path = &profile_def->io_paths[i]; + + if (!top_level_paths.has(io_path->top_level_path)) { + top_level_paths.push_back(io_path->top_level_path); + } + } + + 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); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainer"))); + + VBoxContainer *container = memnew(VBoxContainer); + panel->add_child(container); + + Label *label = memnew(Label); + label->set_text(top_level_paths[i]->display_name); + container->add_child(label); + + for (int j = 0; j < profile_def->io_path_count; j++) { + const OpenXRDefs::IOPath *io_path = &profile_def->io_paths[j]; + if (io_path->top_level_path == top_level_paths[i]) { + _add_io_path(container, io_path); + } + } + } +} + +void OpenXRInteractionProfileEditor::_theme_changed() { + for (int i = 0; i < main_hb->get_child_count(); i++) { + Control *panel = static_cast<Control *>(main_hb->get_child(i)); + if (panel) { + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainer"))); + } + } +} + +OpenXRInteractionProfileEditor::OpenXRInteractionProfileEditor(Ref<OpenXRActionMap> p_action_map, Ref<OpenXRInteractionProfile> p_interaction_profile) : + OpenXRInteractionProfileEditorBase(p_action_map, p_interaction_profile) { + // TODO background of scrollbox should be darker with our VBoxContainers we're adding in _update_interaction_profile the normal color + + main_hb = memnew(HBoxContainer); + add_child(main_hb); + + select_action_dialog = memnew(OpenXRSelectActionDialog(p_action_map)); + select_action_dialog->connect("action_selected", callable_mp(this, &OpenXRInteractionProfileEditor::action_selected)); + add_child(select_action_dialog); + + _update_interaction_profile(); +} diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.h b/modules/openxr/editor/openxr_interaction_profile_editor.h new file mode 100644 index 0000000000..20a37a80eb --- /dev/null +++ b/modules/openxr/editor/openxr_interaction_profile_editor.h @@ -0,0 +1,83 @@ +/*************************************************************************/ +/* openxr_interaction_profile_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_INTERACTION_PROFILE_EDITOR_H +#define OPENXR_INTERACTION_PROFILE_EDITOR_H + +#include "../action_map/openxr_action_map.h" +#include "../action_map/openxr_defs.h" +#include "../action_map/openxr_interaction_profile.h" +#include "scene/gui/scroll_container.h" + +#include "openxr_select_action_dialog.h" + +class OpenXRInteractionProfileEditorBase : public ScrollContainer { + GDCLASS(OpenXRInteractionProfileEditorBase, ScrollContainer); + +protected: + Ref<OpenXRInteractionProfile> interaction_profile; + Ref<OpenXRActionMap> action_map; + + static void _bind_methods(); + void _notification(int p_what); + + const OpenXRDefs::InteractionProfile *profile_def = nullptr; + +public: + Ref<OpenXRInteractionProfile> get_interaction_profile() { return interaction_profile; } + + virtual void _update_interaction_profile() {} + virtual void _theme_changed() {} + void _add_binding(const String p_action, const String p_path); + void _remove_binding(const String p_action, const String p_path); + + OpenXRInteractionProfileEditorBase(Ref<OpenXRActionMap> p_action_map, Ref<OpenXRInteractionProfile> p_interaction_profile); +}; + +class OpenXRInteractionProfileEditor : public OpenXRInteractionProfileEditorBase { + GDCLASS(OpenXRInteractionProfileEditor, OpenXRInteractionProfileEditorBase); + +private: + String selecting_for_io_path; + HBoxContainer *main_hb = nullptr; + OpenXRSelectActionDialog *select_action_dialog = nullptr; + + void _add_io_path(VBoxContainer *p_container, const OpenXRDefs::IOPath *p_io_path); + +public: + void select_action_for(const String p_io_path); + void action_selected(const String p_action); + + virtual void _update_interaction_profile() override; + virtual void _theme_changed() override; + OpenXRInteractionProfileEditor(Ref<OpenXRActionMap> p_action_map, Ref<OpenXRInteractionProfile> p_interaction_profile); +}; + +#endif // OPENXR_INTERACTION_PROFILE_EDITOR_H diff --git a/modules/openxr/editor/openxr_select_action_dialog.cpp b/modules/openxr/editor/openxr_select_action_dialog.cpp new file mode 100644 index 0000000000..80e58044d5 --- /dev/null +++ b/modules/openxr/editor/openxr_select_action_dialog.cpp @@ -0,0 +1,133 @@ +/*************************************************************************/ +/* openxr_select_action_dialog.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_select_action_dialog.h" +#include "editor/editor_node.h" + +void OpenXRSelectActionDialog::_bind_methods() { + ADD_SIGNAL(MethodInfo("action_selected", PropertyInfo(Variant::STRING, "action"))); +} + +void OpenXRSelectActionDialog::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + } break; + } +} + +void OpenXRSelectActionDialog::_on_select_action(const String p_action) { + if (selected_action != "") { + NodePath button_path = action_buttons[selected_action]; + Button *button = static_cast<Button *>(get_node(button_path)); + if (button != nullptr) { + button->set_flat(true); + } + } + + selected_action = p_action; + + if (selected_action != "") { + NodePath button_path = action_buttons[selected_action]; + Button *button = static_cast<Button *>(get_node(button_path)); + if (button != nullptr) { + button->set_flat(false); + } + } +} + +void OpenXRSelectActionDialog::open() { + ERR_FAIL_COND(action_map.is_null()); + + // out with the old... + while (main_vb->get_child_count() > 0) { + memdelete(main_vb->get_child(0)); + } + + selected_action = ""; + action_buttons.clear(); + + Array action_sets = action_map->get_action_sets(); + for (int i = 0; i < action_sets.size(); i++) { + Ref<OpenXRActionSet> action_set = action_sets[i]; + + Label *action_set_label = memnew(Label); + action_set_label->set_text(action_set->get_localized_name()); + main_vb->add_child(action_set_label); + + Array actions = action_set->get_actions(); + for (int j = 0; j < actions.size(); j++) { + Ref<OpenXRAction> action = actions[j]; + + HBoxContainer *action_hb = memnew(HBoxContainer); + main_vb->add_child(action_hb); + + Control *indent_node = memnew(Control); + indent_node->set_custom_minimum_size(Size2(10.0, 0.0)); + action_hb->add_child(indent_node); + + Button *action_button = memnew(Button); + String action_name = action->get_name_with_set(); + action_button->set_flat(true); + action_button->set_text(action->get_name() + ": " + action->get_localized_name()); + action_button->connect("pressed", callable_mp(this, &OpenXRSelectActionDialog::_on_select_action).bind(action_name)); + action_hb->add_child(action_button); + + action_buttons[action_name] = action_button->get_path(); + } + } + + popup_centered(); +} + +void OpenXRSelectActionDialog::ok_pressed() { + if (selected_action == "") { + return; + } + + emit_signal("action_selected", selected_action); + + hide(); +} + +OpenXRSelectActionDialog::OpenXRSelectActionDialog(Ref<OpenXRActionMap> p_action_map) { + action_map = p_action_map; + + set_title(TTR("Select an action")); + + scroll = memnew(ScrollContainer); + scroll->set_custom_minimum_size(Size2(600.0, 400.0)); + add_child(scroll); + + main_vb = memnew(VBoxContainer); + main_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + scroll->add_child(main_vb); +} diff --git a/modules/openxr/editor/openxr_select_action_dialog.h b/modules/openxr/editor/openxr_select_action_dialog.h new file mode 100644 index 0000000000..cbe1380e18 --- /dev/null +++ b/modules/openxr/editor/openxr_select_action_dialog.h @@ -0,0 +1,67 @@ +/*************************************************************************/ +/* openxr_select_action_dialog.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_SELECT_ACTION_DIALOG_H +#define OPENXR_SELECT_ACTION_DIALOG_H + +#include "../action_map/openxr_action_map.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/scroll_container.h" +#include "scene/gui/separator.h" +#include "scene/gui/text_edit.h" + +class OpenXRSelectActionDialog : public ConfirmationDialog { + GDCLASS(OpenXRSelectActionDialog, ConfirmationDialog); + +private: + Ref<OpenXRActionMap> action_map; + String selected_action; + Dictionary action_buttons; + + VBoxContainer *main_vb = nullptr; + ScrollContainer *scroll = nullptr; + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + void _on_select_action(const String p_action); + void open(); + virtual void ok_pressed() override; + + OpenXRSelectActionDialog(Ref<OpenXRActionMap> p_action_map); +}; + +#endif // OPENXR_SELECT_ACTION_DIALOG_H diff --git a/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp new file mode 100644 index 0000000000..23b025db08 --- /dev/null +++ b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp @@ -0,0 +1,123 @@ +/*************************************************************************/ +/* openxr_select_interaction_profile_dialog.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_select_interaction_profile_dialog.h" + +void OpenXRSelectInteractionProfileDialog::_bind_methods() { + ADD_SIGNAL(MethodInfo("interaction_profile_selected", PropertyInfo(Variant::STRING, "interaction_profile"))); +} + +void OpenXRSelectInteractionProfileDialog::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + } break; + } +} + +void OpenXRSelectInteractionProfileDialog::_on_select_interaction_profile(const String p_interaction_profile) { + if (selected_interaction_profile != "") { + NodePath button_path = ip_buttons[selected_interaction_profile]; + Button *button = static_cast<Button *>(get_node(button_path)); + if (button != nullptr) { + button->set_flat(true); + } + } + + selected_interaction_profile = p_interaction_profile; + + if (selected_interaction_profile != "") { + NodePath button_path = ip_buttons[selected_interaction_profile]; + Button *button = static_cast<Button *>(get_node(button_path)); + if (button != nullptr) { + button->set_flat(false); + } + } +} + +void OpenXRSelectInteractionProfileDialog::open(PackedStringArray p_do_not_include) { + int available_count = 0; + + // out with the old... + while (main_vb->get_child_count() > 0) { + memdelete(main_vb->get_child(0)); + } + + selected_interaction_profile = ""; + ip_buttons.clear(); + + // in with the new + PackedStringArray interaction_profiles = OpenXRDefs::get_interaction_profile_paths(); + for (int i = 0; i < interaction_profiles.size(); i++) { + String path = interaction_profiles[i]; + if (!p_do_not_include.has(path)) { + Button *ip_button = memnew(Button); + ip_button->set_flat(true); + ip_button->set_text(OpenXRDefs::get_profile(path)->display_name); + ip_button->connect("pressed", callable_mp(this, &OpenXRSelectInteractionProfileDialog::_on_select_interaction_profile).bind(path)); + main_vb->add_child(ip_button); + + ip_buttons[path] = ip_button->get_path(); + available_count++; + } + } + + if (available_count == 0) { + // give warning that we have all profiles selected + + } else { + // TODO maybe if we only have one, auto select it? + + popup_centered(); + } +} + +void OpenXRSelectInteractionProfileDialog::ok_pressed() { + if (selected_interaction_profile == "") { + return; + } + + emit_signal("interaction_profile_selected", selected_interaction_profile); + + hide(); +} + +OpenXRSelectInteractionProfileDialog::OpenXRSelectInteractionProfileDialog() { + set_title("Select an interaction profile"); + + scroll = memnew(ScrollContainer); + scroll->set_custom_minimum_size(Size2(600.0, 400.0)); + add_child(scroll); + + main_vb = memnew(VBoxContainer); + // main_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + scroll->add_child(main_vb); +} diff --git a/modules/openxr/editor/openxr_select_interaction_profile_dialog.h b/modules/openxr/editor/openxr_select_interaction_profile_dialog.h new file mode 100644 index 0000000000..54bfe3120a --- /dev/null +++ b/modules/openxr/editor/openxr_select_interaction_profile_dialog.h @@ -0,0 +1,66 @@ +/*************************************************************************/ +/* openxr_select_interaction_profile_dialog.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_SELECT_INTERACTION_PROFILE_DIALOG_H +#define OPENXR_SELECT_INTERACTION_PROFILE_DIALOG_H + +#include "../action_map/openxr_defs.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/scroll_container.h" +#include "scene/gui/separator.h" +#include "scene/gui/text_edit.h" + +class OpenXRSelectInteractionProfileDialog : public ConfirmationDialog { + GDCLASS(OpenXRSelectInteractionProfileDialog, ConfirmationDialog); + +private: + String selected_interaction_profile; + Dictionary ip_buttons; + + VBoxContainer *main_vb = nullptr; + ScrollContainer *scroll = nullptr; + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + void _on_select_interaction_profile(const String p_interaction_profile); + void open(PackedStringArray p_do_not_include); + virtual void ok_pressed() override; + + OpenXRSelectInteractionProfileDialog(); +}; + +#endif // OPENXR_SELECT_INTERACTION_PROFILE_DIALOG_H diff --git a/modules/openxr/extensions/openxr_android_extension.h b/modules/openxr/extensions/openxr_android_extension.h index e102197a55..88b0e310e7 100644 --- a/modules/openxr/extensions/openxr_android_extension.h +++ b/modules/openxr/extensions/openxr_android_extension.h @@ -44,4 +44,4 @@ private: static OpenXRAndroidExtension *singleton; }; -#endif // !OPENXR_ANDROID_EXTENSION_H +#endif // OPENXR_ANDROID_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h index 5242ee6063..ecc6e0dd4e 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper.h +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -32,26 +32,27 @@ #define OPENXR_EXTENSION_WRAPPER_H #include "core/error/error_macros.h" -#include "core/math/camera_matrix.h" -#include "core/templates/map.h" +#include "core/math/projection.h" +#include "core/templates/hash_map.h" #include "core/templates/rid.h" #include "thirdparty/openxr/src/common/xr_linear.h" #include <openxr/openxr.h> class OpenXRAPI; +class OpenXRActionMap; class OpenXRExtensionWrapper { protected: - OpenXRAPI *openxr_api; + OpenXRAPI *openxr_api = nullptr; // Store extension we require. // If bool pointer is a nullptr this means this extension is mandatory and initialisation will fail if it is not available // If bool pointer is set, value will be set to true or false depending on whether extension is available - Map<const char *, bool *> request_extensions; + HashMap<String, bool *> request_extensions; public: - virtual Map<const char *, bool *> get_request_extensions() { + virtual HashMap<String, bool *> get_request_extensions() { return request_extensions; } @@ -96,11 +97,11 @@ public: virtual String get_swapchain_format_name(int64_t p_swapchain_format) const = 0; virtual bool get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) = 0; virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) = 0; - virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, CameraMatrix &r_camera_matrix) = 0; + virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) = 0; virtual bool copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) = 0; OpenXRGraphicsExtensionWrapper(OpenXRAPI *p_openxr_api) : OpenXRExtensionWrapper(p_openxr_api){}; }; -#endif // ~OPENXR_EXTENSION_WRAPPER_H +#endif // OPENXR_EXTENSION_WRAPPER_H diff --git a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp new file mode 100644 index 0000000000..302acf4e30 --- /dev/null +++ b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp @@ -0,0 +1,67 @@ +/*************************************************************************/ +/* openxr_htc_vive_tracker_extension.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_htc_vive_tracker_extension.h" +#include "core/string/print_string.h" + +OpenXRHTCViveTrackerExtension *OpenXRHTCViveTrackerExtension::singleton = nullptr; + +OpenXRHTCViveTrackerExtension *OpenXRHTCViveTrackerExtension::get_singleton() { + return singleton; +} + +OpenXRHTCViveTrackerExtension::OpenXRHTCViveTrackerExtension(OpenXRAPI *p_openxr_api) : + OpenXRExtensionWrapper(p_openxr_api) { + singleton = this; + + request_extensions[XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME] = &available; +} + +OpenXRHTCViveTrackerExtension::~OpenXRHTCViveTrackerExtension() { + singleton = nullptr; +} + +bool OpenXRHTCViveTrackerExtension::is_available() { + return available; +} + +bool OpenXRHTCViveTrackerExtension::on_event_polled(const XrEventDataBuffer &event) { + switch (event.type) { + case XR_TYPE_EVENT_DATA_VIVE_TRACKER_CONNECTED_HTCX: { + // Investigate if we need to do more here + print_verbose("OpenXR EVENT: VIVE tracker connected"); + + return true; + } break; + default: { + return false; + } break; + } +} diff --git a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.h b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.h new file mode 100644 index 0000000000..7f37351f27 --- /dev/null +++ b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.h @@ -0,0 +1,52 @@ +/*************************************************************************/ +/* openxr_htc_vive_tracker_extension.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_HTC_VIVE_TRACKER_EXTENSION_H +#define OPENXR_HTC_VIVE_TRACKER_EXTENSION_H + +#include "openxr_extension_wrapper.h" + +class OpenXRHTCViveTrackerExtension : public OpenXRExtensionWrapper { +public: + static OpenXRHTCViveTrackerExtension *get_singleton(); + + OpenXRHTCViveTrackerExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXRHTCViveTrackerExtension() override; + + bool is_available(); + virtual bool on_event_polled(const XrEventDataBuffer &event) override; + +private: + static OpenXRHTCViveTrackerExtension *singleton; + + bool available = false; +}; + +#endif // OPENXR_HTC_VIVE_TRACKER_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_vulkan_extension.cpp b/modules/openxr/extensions/openxr_vulkan_extension.cpp index a481c6f979..2608c4ac17 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.cpp +++ b/modules/openxr/extensions/openxr_vulkan_extension.cpp @@ -30,10 +30,11 @@ #include "core/string/print_string.h" -#include "modules/openxr/extensions/openxr_vulkan_extension.h" -#include "modules/openxr/openxr_api.h" -#include "modules/openxr/openxr_util.h" -#include "servers/rendering/renderer_rd/renderer_storage_rd.h" +#include "../extensions/openxr_vulkan_extension.h" +#include "../openxr_api.h" +#include "../openxr_util.h" +#include "servers/rendering/renderer_rd/effects/copy_effects.h" +#include "servers/rendering/renderer_rd/storage_rd/texture_storage.h" #include "servers/rendering/rendering_server_globals.h" #include "servers/rendering_server.h" @@ -325,10 +326,65 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in *r_swapchain_graphics_data = data; data->is_multiview = (p_array_size > 1); - RenderingDevice::DataFormat format = RenderingDevice::DATA_FORMAT_R8G8B8A8_SRGB; // TODO set this based on p_swapchain_format - RenderingDevice::TextureSamples samples = RenderingDevice::TEXTURE_SAMPLES_1; // TODO set this based on p_sample_count + RenderingDevice::DataFormat format = RenderingDevice::DATA_FORMAT_R8G8B8A8_SRGB; + RenderingDevice::TextureSamples samples = RenderingDevice::TEXTURE_SAMPLES_1; uint64_t usage_flags = RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT | RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; + switch (p_swapchain_format) { + case VK_FORMAT_R8G8B8A8_SRGB: + // Even though this is an sRGB framebuffer format we're using UNORM here. + // The reason here is because Godot does a linear to sRGB conversion while + // with the sRGB format, this conversion would be doubled by the hardware. + // This also means we're reading the values as is for our preview on screen. + // The OpenXR runtime however is still treating this as an sRGB format and + // will thus do an sRGB -> Linear conversion as expected. + // format = RenderingDevice::DATA_FORMAT_R8G8B8A8_SRGB; + format = RenderingDevice::DATA_FORMAT_R8G8B8A8_UNORM; + break; + case VK_FORMAT_B8G8R8A8_SRGB: + // format = RenderingDevice::DATA_FORMAT_B8G8R8A8_SRGB; + format = RenderingDevice::DATA_FORMAT_B8G8R8A8_UNORM; + break; + case VK_FORMAT_R8G8B8A8_UINT: + format = RenderingDevice::DATA_FORMAT_R8G8B8A8_UINT; + break; + case VK_FORMAT_B8G8R8A8_UINT: + format = RenderingDevice::DATA_FORMAT_B8G8R8A8_UINT; + break; + default: + // continue with our default value + print_line("Unsupported swapchain format ", p_swapchain_format); + break; + } + + switch (p_sample_count) { + case 1: + samples = RenderingDevice::TEXTURE_SAMPLES_1; + break; + case 2: + samples = RenderingDevice::TEXTURE_SAMPLES_2; + break; + case 4: + samples = RenderingDevice::TEXTURE_SAMPLES_4; + break; + case 8: + samples = RenderingDevice::TEXTURE_SAMPLES_8; + break; + case 16: + samples = RenderingDevice::TEXTURE_SAMPLES_16; + break; + case 32: + samples = RenderingDevice::TEXTURE_SAMPLES_32; + break; + case 64: + samples = RenderingDevice::TEXTURE_SAMPLES_64; + break; + default: + // continue with our default value + print_line("Unsupported sample count ", p_sample_count); + break; + } + Vector<RID> image_rids; Vector<RID> framebuffers; @@ -364,7 +420,7 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in return true; } -bool OpenXRVulkanExtension::create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, CameraMatrix &r_camera_matrix) { +bool OpenXRVulkanExtension::create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) { // Even though this is a Vulkan renderer we're using OpenGL coordinate systems XrMatrix4x4f matrix; XrMatrix4x4f_CreateProjectionFov(&matrix, GRAPHICS_OPENGL, p_fov, (float)p_z_near, (float)p_z_far); @@ -382,9 +438,8 @@ bool OpenXRVulkanExtension::copy_render_target_to_image(RID p_from_render_target SwapchainGraphicsData *data = (SwapchainGraphicsData *)p_swapchain_graphics_data; ERR_FAIL_NULL_V(data, false); ERR_FAIL_COND_V(p_from_render_target.is_null(), false); - ERR_FAIL_NULL_V(RendererStorageRD::base_singleton, false); - RID source_image = RendererStorageRD::base_singleton->render_target_get_rd_texture(p_from_render_target); + RID source_image = RendererRD::TextureStorage::get_singleton()->render_target_get_rd_texture(p_from_render_target); ERR_FAIL_COND_V(source_image.is_null(), false); RID depth_image; // TODO implement @@ -394,11 +449,9 @@ bool OpenXRVulkanExtension::copy_render_target_to_image(RID p_from_render_target ERR_FAIL_COND_V(fb.is_null(), false); // Our vulkan extension can only be used in conjunction with our vulkan renderer. - // We need access to the effects object in order to have access to our copy logic. - // Breaking all the rules but there is no nice way to do this. - EffectsRD *effects = RendererStorageRD::base_singleton->get_effects(); - ERR_FAIL_NULL_V(effects, false); - effects->copy_to_fb_rect(source_image, fb, Rect2i(), false, false, false, false, depth_image, data->is_multiview); + RendererRD::CopyEffects *copy_effects = RendererRD::CopyEffects::get_singleton(); + ERR_FAIL_NULL_V(copy_effects, false); + copy_effects->copy_to_fb_rect(source_image, fb, Rect2i(), false, false, false, false, depth_image, data->is_multiview); return true; } diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/openxr_vulkan_extension.h index cf55ae264f..5dddc4b9c9 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.h +++ b/modules/openxr/extensions/openxr_vulkan_extension.h @@ -63,7 +63,7 @@ public: virtual String get_swapchain_format_name(int64_t p_swapchain_format) const override; virtual bool get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) override; virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) override; - virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, CameraMatrix &r_camera_matrix) override; + virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) override; virtual bool copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) override; private: @@ -78,11 +78,11 @@ private: bool check_graphics_api_support(XrVersion p_desired_version); - VkInstance vulkan_instance; - VkPhysicalDevice vulkan_physical_device; - VkDevice vulkan_device; - uint32_t vulkan_queue_family_index; - uint32_t vulkan_queue_index; + VkInstance vulkan_instance = nullptr; + VkPhysicalDevice vulkan_physical_device = nullptr; + VkDevice vulkan_device = nullptr; + uint32_t vulkan_queue_family_index = 0; + uint32_t vulkan_queue_index = 0; XrResult xrGetVulkanGraphicsRequirements2KHR(XrInstance p_instance, XrSystemId p_system_id, XrGraphicsRequirementsVulkanKHR *p_graphics_requirements); XrResult xrCreateVulkanInstanceKHR(XrInstance p_instance, const XrVulkanInstanceCreateInfoKHR *p_create_info, VkInstance *r_vulkan_instance, VkResult *r_vulkan_result); @@ -90,4 +90,4 @@ private: XrResult xrCreateVulkanDeviceKHR(XrInstance p_instance, const XrVulkanDeviceCreateInfoKHR *p_create_info, VkDevice *r_device, VkResult *r_result); }; -#endif // !OPENXR_VULKAN_EXTENSION_H +#endif // OPENXR_VULKAN_EXTENSION_H diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 23fd0404d5..92d074cb75 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -48,66 +48,29 @@ #include "extensions/openxr_vulkan_extension.h" #endif -OpenXRAPI *OpenXRAPI::singleton = nullptr; - -void OpenXRAPI::setup_global_defs() { - // As OpenXRAPI is not constructed if OpenXR is not enabled, we register our project and editor settings here - - // Project settings - GLOBAL_DEF_BASIC("xr/openxr/enabled", false); - GLOBAL_DEF_BASIC("xr/openxr/default_action_map", "res://default_action_map.tres"); - ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/default_action_map", PropertyInfo(Variant::STRING, "xr/openxr/default_action_map", PROPERTY_HINT_FILE, "*.tres")); +#include "extensions/openxr_htc_vive_tracker_extension.h" - GLOBAL_DEF_BASIC("xr/openxr/form_factor", "0"); - ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/form_factor", PropertyInfo(Variant::INT, "xr/openxr/form_factor", PROPERTY_HINT_ENUM, "Head mounted,Handheld")); +#include "modules/openxr/openxr_interface.h" - GLOBAL_DEF_BASIC("xr/openxr/view_configuration", "1"); - ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/view_configuration", PropertyInfo(Variant::INT, "xr/openxr/view_configuration", PROPERTY_HINT_ENUM, "Mono,Stereo")); // "Mono,Stereo,Quad,Observer" - - GLOBAL_DEF_BASIC("xr/openxr/reference_space", "1"); - ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/reference_space", PropertyInfo(Variant::INT, "xr/openxr/reference_space", PROPERTY_HINT_ENUM, "Local,Stage")); - -#ifdef TOOLS_ENABLED - // Disabled for now, using XR inside of the editor we'll be working on during the coming months. - - // editor settings (it seems we're too early in the process when setting up rendering, to access editor settings...) - // EDITOR_DEF_RST("xr/openxr/in_editor", false); - // GLOBAL_DEF("xr/openxr/in_editor", false); -#endif -} +OpenXRAPI *OpenXRAPI::singleton = nullptr; -bool OpenXRAPI::openxr_is_enabled() { +bool OpenXRAPI::openxr_is_enabled(bool p_check_run_in_editor) { // @TODO we need an overrule switch so we can force enable openxr, i.e run "godot --openxr_enabled" - if (Engine::get_singleton()->is_editor_hint()) { -#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) { // Disabled for now, using XR inside of the editor we'll be working on during the coming months. return false; - - // bool enabled = GLOBAL_GET("xr/openxr/in_editor"); // EDITOR_GET("xr/openxr/in_editor"); - // return enabled; -#else - // we should never get here, editor hint won't be true if the editor isn't compiled in. - return false; -#endif } else { - bool enabled = GLOBAL_GET("xr/openxr/enabled"); - return enabled; + if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) { + return GLOBAL_GET("xr/openxr/enabled"); + } else { + return XRServer::get_xr_mode() == XRServer::XRMODE_ON; + } } } OpenXRAPI *OpenXRAPI::get_singleton() { - if (singleton != nullptr) { - // already constructed, return our singleton - return singleton; - } else if (openxr_is_enabled()) { - // construct our singleton and return it - singleton = memnew(OpenXRAPI); - return singleton; - } else { - // not enabled, don't instantiate, return nullptr - return nullptr; - } + return singleton; } String OpenXRAPI::get_default_action_map_resource_name() { @@ -134,7 +97,7 @@ String OpenXRAPI::get_error_string(XrResult result) { } String OpenXRAPI::get_swapchain_format_name(int64_t p_swapchain_format) const { - // This is rendering engine dependend... + // This is rendering engine dependent... if (graphics_extension) { return graphics_extension->get_swapchain_format_name(p_swapchain_format); } @@ -143,7 +106,7 @@ String OpenXRAPI::get_swapchain_format_name(int64_t p_swapchain_format) const { } bool OpenXRAPI::load_layer_properties() { - // This queries additional layers that are available and can be initialised when we create our OpenXR instance + // This queries additional layers that are available and can be initialized when we create our OpenXR instance if (layer_properties != nullptr) { // already retrieved this return true; @@ -173,7 +136,7 @@ bool OpenXRAPI::load_layer_properties() { } bool OpenXRAPI::load_supported_extensions() { - // This queries supported extensions that are available and can be initialised when we create our OpenXR instance + // This queries supported extensions that are available and can be initialized when we create our OpenXR instance if (supported_extensions != nullptr) { // already retrieved this @@ -204,13 +167,20 @@ bool OpenXRAPI::load_supported_extensions() { return true; } -bool OpenXRAPI::is_extension_supported(const char *p_extension) const { +bool OpenXRAPI::is_extension_supported(const String &p_extension) const { for (uint32_t i = 0; i < num_supported_extensions; i++) { - if (strcmp(supported_extensions[i].extensionName, p_extension)) { + if (supported_extensions[i].extensionName == p_extension) { +#ifdef DEBUG + print_line("OpenXR: requested extension", p_extension, "is supported"); +#endif return true; } } +#ifdef DEBUG + print_line("OpenXR: requested extension", p_extension, "is not supported"); +#endif + return false; } @@ -231,9 +201,9 @@ bool OpenXRAPI::create_instance() { // Create our OpenXR instance, this will query any registered extension wrappers for extensions we need to enable. // Append the extensions requested by the registered extension wrappers. - Map<const char *, bool *> requested_extensions; + HashMap<String, bool *> requested_extensions; for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { - Map<const char *, bool *> wrapper_request_extensions = wrapper->get_request_extensions(); + const HashMap<String, bool *> &wrapper_request_extensions = wrapper->get_request_extensions(); // requested_extensions.insert(wrapper_request_extensions.begin(), wrapper_request_extensions.end()); for (auto &requested_extension : wrapper_request_extensions) { @@ -241,8 +211,17 @@ bool OpenXRAPI::create_instance() { } } + // Add optional extensions for controllers that may be supported. + // Overkill to create extension classes for this. + requested_extensions[XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME] = &ext_hp_mixed_reality_available; + requested_extensions[XR_EXT_SAMSUNG_ODYSSEY_CONTROLLER_EXTENSION_NAME] = &ext_samsung_odyssey_available; + requested_extensions[XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME] = &ext_vive_cosmos_available; + requested_extensions[XR_HTC_VIVE_FOCUS3_CONTROLLER_INTERACTION_EXTENSION_NAME] = &ext_vive_focus3_available; + requested_extensions[XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME] = &ext_huawei_controller_available; + // Check which extensions are supported enabled_extensions.clear(); + for (auto &requested_extension : requested_extensions) { if (!is_extension_supported(requested_extension.key)) { if (requested_extension.value == nullptr) { @@ -257,13 +236,18 @@ bool OpenXRAPI::create_instance() { *requested_extension.value = true; // and record that we want to enable it - enabled_extensions.push_back(requested_extension.key); + enabled_extensions.push_back(requested_extension.key.ascii()); } else { // record that we want to enable this - enabled_extensions.push_back(requested_extension.key); + enabled_extensions.push_back(requested_extension.key.ascii()); } } + Vector<const char *> extension_ptrs; + for (int i = 0; i < enabled_extensions.size(); i++) { + extension_ptrs.push_back(enabled_extensions[i].get_data()); + } + // Get our project name String project_name = GLOBAL_GET("application/config/name"); @@ -283,8 +267,8 @@ bool OpenXRAPI::create_instance() { application_info, // applicationInfo 0, // enabledApiLayerCount, need to find out if we need support for this? nullptr, // enabledApiLayerNames - uint32_t(enabled_extensions.size()), // enabledExtensionCount - enabled_extensions.ptr() // enabledExtensionNames + uint32_t(extension_ptrs.size()), // enabledExtensionCount + extension_ptrs.ptr() // enabledExtensionNames }; copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE); @@ -373,7 +357,7 @@ bool OpenXRAPI::get_system_info() { } bool OpenXRAPI::load_supported_view_configuration_types() { - // This queries the supported configuration types, likely there will only be one chosing between Mono (phone AR) and Stereo (HMDs) + // This queries the supported configuration types, likely there will only be one choosing between Mono (phone AR) and Stereo (HMDs) ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); @@ -442,7 +426,7 @@ bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType for (uint32_t i = 0; i < view_count; i++) { view_configuration_views[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW; - view_configuration_views[i].next = NULL; + view_configuration_views[i].next = nullptr; } result = xrEnumerateViewConfigurationViews(instance, system_id, p_configuration_type, view_count, &view_count, view_configuration_views); @@ -714,10 +698,10 @@ bool OpenXRAPI::create_main_swapchain() { for (uint32_t i = 0; i < view_count; i++) { views[i].type = XR_TYPE_VIEW; - views[i].next = NULL; + views[i].next = nullptr; projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; - projection_views[i].next = NULL; + projection_views[i].next = nullptr; projection_views[i].subImage.swapchain = swapchain; projection_views[i].subImage.imageArrayIndex = i; projection_views[i].subImage.imageRect.offset.x = 0; @@ -819,7 +803,7 @@ bool OpenXRAPI::create_swapchain(int64_t p_swapchain_format, uint32_t p_width, u return false; } - if (!graphics_extension->get_swapchain_image_data(new_swapchain, p_swapchain_format, p_width, p_height, p_array_size, p_array_size, r_swapchain_graphics_data)) { + if (!graphics_extension->get_swapchain_image_data(new_swapchain, p_swapchain_format, p_width, p_height, p_sample_count, p_array_size, r_swapchain_graphics_data)) { xrDestroySwapchain(new_swapchain); return false; } @@ -877,7 +861,9 @@ bool OpenXRAPI::on_state_ready() { wrapper->on_state_ready(); } - // TODO emit signal + if (xr_interface) { + xr_interface->on_state_ready(); + } // TODO Tell android @@ -889,6 +875,13 @@ bool OpenXRAPI::on_state_synchronized() { print_line("On state synchronized"); #endif + // Just in case, see if we already have active trackers... + List<RID> trackers; + tracker_owner.get_owned_list(&trackers); + for (int i = 0; i < trackers.size(); i++) { + tracker_check_profile(trackers[i]); + } + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_state_synchronized(); } @@ -905,7 +898,9 @@ bool OpenXRAPI::on_state_visible() { wrapper->on_state_visible(); } - // TODO emit signal + if (xr_interface) { + xr_interface->on_state_visible(); + } return true; } @@ -919,7 +914,9 @@ bool OpenXRAPI::on_state_focused() { wrapper->on_state_focused(); } - // TODO emit signal + if (xr_interface) { + xr_interface->on_state_focused(); + } return true; } @@ -929,7 +926,9 @@ bool OpenXRAPI::on_state_stopping() { print_line("On state stopping"); #endif - // TODO emit signal + if (xr_interface) { + xr_interface->on_state_stopping(); + } for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_state_stopping(); @@ -993,7 +992,7 @@ bool OpenXRAPI::is_running() { return running; } -bool OpenXRAPI::initialise(const String &p_rendering_driver) { +bool OpenXRAPI::initialize(const String &p_rendering_driver) { ERR_FAIL_COND_V_MSG(instance != XR_NULL_HANDLE, false, "OpenXR instance was already created"); if (p_rendering_driver == "vulkan") { @@ -1005,7 +1004,7 @@ bool OpenXRAPI::initialise(const String &p_rendering_driver) { ERR_FAIL_V(false); #endif } else if (p_rendering_driver == "opengl3") { -#ifdef OPENGL3_ENABLED +#ifdef GLES3_ENABLED // graphics_extension = memnew(OpenXROpenGLExtension(this)); // register_extension_wrapper(graphics_extension); ERR_FAIL_V_MSG(false, "OpenXR: OpenGL is not supported at this time."); @@ -1017,7 +1016,7 @@ bool OpenXRAPI::initialise(const String &p_rendering_driver) { ERR_FAIL_V_MSG(false, "OpenXR: Unsupported rendering device."); } - // initialise + // initialize if (!load_layer_properties()) { destroy_instance(); return false; @@ -1051,7 +1050,7 @@ bool OpenXRAPI::initialise(const String &p_rendering_driver) { return true; } -bool OpenXRAPI::initialise_session() { +bool OpenXRAPI::initialize_session() { if (!create_session()) { destroy_session(); return false; @@ -1081,6 +1080,10 @@ void OpenXRAPI::finish() { destroy_instance(); } +void OpenXRAPI::set_xr_interface(OpenXRInterface *p_xr_interface) { + xr_interface = p_xr_interface; +} + void OpenXRAPI::register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper) { registered_extension_wrappers.push_back(p_extension_wrapper); } @@ -1162,7 +1165,7 @@ bool OpenXRAPI::get_view_transform(uint32_t p_view, Transform3D &r_transform) { } // we don't have valid view info - if (views == NULL || !view_pose_valid) { + if (views == nullptr || !view_pose_valid) { return false; } @@ -1172,7 +1175,7 @@ bool OpenXRAPI::get_view_transform(uint32_t p_view, Transform3D &r_transform) { return true; } -bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, CameraMatrix &p_camera_matrix) { +bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, Projection &p_camera_matrix) { ERR_FAIL_COND_V(!running, false); ERR_FAIL_NULL_V(graphics_extension, false); @@ -1182,7 +1185,7 @@ bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z } // we don't have valid view info - if (views == NULL || !view_pose_valid) { + if (views == nullptr || !view_pose_valid) { return false; } @@ -1204,20 +1207,38 @@ bool OpenXRAPI::poll_events() { handled |= wrapper->on_event_polled(runtimeEvent); } switch (runtimeEvent.type) { - // case XR_TYPE_EVENT_DATA_EVENTS_LOST: { - // } break; - // case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: { - // } break; - // case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { - // } break; + case XR_TYPE_EVENT_DATA_EVENTS_LOST: { + XrEventDataEventsLost *event = (XrEventDataEventsLost *)&runtimeEvent; + + // We probably didn't poll fast enough, just output warning + WARN_PRINT("OpenXR EVENT: " + itos(event->lostEventCount) + " event data lost!"); + } break; + case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: { + // XrEventDataVisibilityMaskChangedKHR *event = (XrEventDataVisibilityMaskChangedKHR *)&runtimeEvent; + + // TODO implement this in the future, we should call xrGetVisibilityMaskKHR to obtain a mask, + // this will allow us to prevent rendering the part of our view which is never displayed giving us + // a decent performance improvement. + + print_verbose("OpenXR EVENT: STUB: visibility mask changed"); + } break; + case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { + XrEventDataInstanceLossPending *event = (XrEventDataInstanceLossPending *)&runtimeEvent; + + // TODO We get this event if we're about to loose our OpenXR instance. + // We should queue exiting Godot at this point. + + print_verbose("OpenXR EVENT: instance loss pending at " + itos(event->lossTime)); + return false; + } break; case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { XrEventDataSessionStateChanged *event = (XrEventDataSessionStateChanged *)&runtimeEvent; session_state = event->state; if (session_state >= XR_SESSION_STATE_MAX_ENUM) { - print_line("OpenXR EVENT: session state changed to UNKNOWN -", session_state); + print_verbose("OpenXR EVENT: session state changed to UNKNOWN - " + itos(session_state)); } else { - print_line("OpenXR EVENT: session state changed to", OpenXRUtil::get_session_state_name(session_state)); + print_verbose("OpenXR EVENT: session state changed to " + OpenXRUtil::get_session_state_name(session_state)); switch (session_state) { case XR_SESSION_STATE_IDLE: @@ -1249,13 +1270,29 @@ bool OpenXRAPI::poll_events() { } } } break; - // case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { - // } break; - // case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { - // } break; + case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { + XrEventDataReferenceSpaceChangePending *event = (XrEventDataReferenceSpaceChangePending *)&runtimeEvent; + + print_verbose("OpenXR EVENT: reference space type " + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!"); + if (event->poseValid && xr_interface) { + xr_interface->on_pose_recentered(); + } + } break; + case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { + print_verbose("OpenXR EVENT: interaction profile changed!"); + + XrEventDataInteractionProfileChanged *event = (XrEventDataInteractionProfileChanged *)&runtimeEvent; + + List<RID> trackers; + tracker_owner.get_owned_list(&trackers); + for (int i = 0; i < trackers.size(); i++) { + tracker_check_profile(trackers[i], event->session); + } + + } break; default: if (!handled) { - print_line("OpenXR Unhandled event type", OpenXRUtil::get_structure_type_name(runtimeEvent.type)); + print_verbose("OpenXR Unhandled event type " + OpenXRUtil::get_structure_type_name(runtimeEvent.type)); } break; } @@ -1348,9 +1385,21 @@ void OpenXRAPI::pre_render() { XrResult result = xrWaitFrame(session, &frame_wait_info, &frame_state); if (XR_FAILED(result)) { print_line("OpenXR: xrWaitFrame() was not successful [", get_error_string(result), "]"); + + // reset just in case + frame_state.predictedDisplayTime = 0; + frame_state.predictedDisplayPeriod = 0; + frame_state.shouldRender = false; + return; } + if (frame_state.predictedDisplayPeriod > 500000000) { + // display period more then 0.5 seconds? must be wrong data + print_verbose("OpenXR resetting invalid display period " + rtos(frame_state.predictedDisplayPeriod)); + frame_state.predictedDisplayPeriod = 0; + } + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_pre_render(); } @@ -1532,7 +1581,7 @@ void OpenXRAPI::end_frame() { OpenXRAPI::OpenXRAPI() { // OpenXRAPI is only constructed if OpenXR is enabled. - // It will be constructed when the rendering device first accesses OpenXR (be it the Vulkan or OpenGL rendering system) + singleton = this; if (Engine::get_singleton()->is_editor_hint()) { // Enabled OpenXR in the editor? Adjust our settings for the editor @@ -1589,9 +1638,12 @@ OpenXRAPI::OpenXRAPI() { frame_state.predictedDisplayPeriod = 0; #ifdef ANDROID_ENABLED - // our android wrapper will initialise our android loader at this point + // our android wrapper will initialize our android loader at this point register_extension_wrapper(memnew(OpenXRAndroidExtension(this))); #endif + + // register our other extensions + register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension(this))); } OpenXRAPI::~OpenXRAPI() { @@ -1616,6 +1668,8 @@ OpenXRAPI::~OpenXRAPI() { memfree(layer_properties); layer_properties = nullptr; } + + singleton = nullptr; } Transform3D OpenXRAPI::transform_from_pose(const XrPosef &p_pose) { @@ -1676,7 +1730,7 @@ XRPose::TrackingConfidence OpenXRAPI::transform_from_location(const XrHandJointL return _transform_from_location(p_location, r_transform); } -void OpenXRAPI::parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 r_angular_velocity) { +void OpenXRAPI::parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) { if (p_velocity.velocityFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) { XrVector3f linear_velocity = p_velocity.linearVelocity; r_linear_velocity = Vector3(linear_velocity.x, linear_velocity.y, linear_velocity.z); @@ -1691,38 +1745,97 @@ void OpenXRAPI::parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_l } } -RID OpenXRAPI::path_create(const String p_name) { - ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); +RID OpenXRAPI::get_tracker_rid(XrPath p_path) { + List<RID> current; + tracker_owner.get_owned_list(¤t); + for (int i = 0; i < current.size(); i++) { + Tracker *tracker = tracker_owner.get_or_null(current[i]); + if (tracker && tracker->toplevel_path == p_path) { + return current[i]; + } + } - // Encoding our path as a RID is probably overkill but it does future proof this - // Note that we only do this for XrPaths that we access from outside of this class! + return RID(); +} - Path new_path; +RID OpenXRAPI::tracker_create(const String p_name) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); - print_line("Parsing path ", p_name); + Tracker new_tracker; + new_tracker.name = 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_path.path); + 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(); } - return xr_path_owner.make_rid(new_path); + return tracker_owner.make_rid(new_tracker); +} + +String OpenXRAPI::tracker_get_name(RID p_tracker) { + if (p_tracker.is_null()) { + return String("None"); + } + + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, String()); + + return tracker->name; } -void OpenXRAPI::path_free(RID p_path) { - Path *path = xr_path_owner.get_or_null(p_path); - ERR_FAIL_NULL(path); +void OpenXRAPI::tracker_check_profile(RID p_tracker, XrSession p_session) { + if (p_session == XR_NULL_HANDLE) { + p_session = session; + } + + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL(tracker); + + if (tracker->toplevel_path == XR_NULL_PATH) { + // no path, how was this even created? + return; + } + + XrInteractionProfileState profile_state = { + XR_TYPE_INTERACTION_PROFILE_STATE, // type + nullptr, // next + XR_NULL_PATH // interactionProfile + }; + + XrResult result = xrGetCurrentInteractionProfile(p_session, tracker->toplevel_path, &profile_state); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get interaction profile for", itos(tracker->toplevel_path), "[", get_error_string(result), "]"); + return; + } + + XrPath new_profile = profile_state.interactionProfile; + XrPath was_profile = get_interaction_profile_path(tracker->active_profile_rid); + if (was_profile != new_profile) { + tracker->active_profile_rid = get_interaction_profile_rid(new_profile); + + if (xr_interface) { + xr_interface->tracker_profile_changed(p_tracker, tracker->active_profile_rid); + } + } +} + +void OpenXRAPI::tracker_free(RID p_tracker) { + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL(tracker); // there is nothing to free here - xr_path_owner.free(p_path); + tracker_owner.free(p_tracker); } RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_name, const int p_priority) { ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); ActionSet action_set; + action_set.name = p_name; action_set.is_attached = false; // create our action set... @@ -1737,7 +1850,7 @@ RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_n copy_string_to_char_buffer(p_name, action_set_info.actionSetName, XR_MAX_ACTION_SET_NAME_SIZE); copy_string_to_char_buffer(p_localized_name, action_set_info.localizedActionSetName, XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE); - print_line("Creating action set ", action_set_info.actionSetName, " - ", action_set_info.localizedActionSetName, " (", itos(action_set_info.priority), ")"); + // print_line("Creating action set ", action_set_info.actionSetName, " - ", action_set_info.localizedActionSetName, " (", itos(action_set_info.priority), ")"); XrResult result = xrCreateActionSet(instance, &action_set_info, &action_set.handle); if (XR_FAILED(result)) { @@ -1748,6 +1861,17 @@ RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_n return action_set_owner.make_rid(action_set); } +String OpenXRAPI::action_set_get_name(RID p_action_set) { + if (p_action_set.is_null()) { + return String("None"); + } + + ActionSet *action_set = action_set_owner.get_or_null(p_action_set); + ERR_FAIL_NULL_V(action_set, String()); + + return action_set->name; +} + bool OpenXRAPI::action_set_attach(RID p_action_set) { ActionSet *action_set = action_set_owner.get_or_null(p_action_set); ERR_FAIL_NULL_V(action_set, false); @@ -1776,6 +1900,24 @@ bool OpenXRAPI::action_set_attach(RID p_action_set) { action_set->is_attached = true; + /* For debugging: + print_verbose("Attached set " + action_set->name); + List<RID> action_rids; + action_owner.get_owned_list(&action_rids); + for (int i = 0; i < action_rids.size(); i++) { + Action * action = action_owner.get_or_null(action_rids[i]); + if (action && action->action_set_rid == p_action_set) { + print_verbose(" - Action " + action->name + ": " + OpenXRUtil::get_action_type_name(action->action_type)); + for (int j = 0; j < action->trackers.size(); j++) { + Tracker * tracker = tracker_owner.get_or_null(action->trackers[j].tracker_rid); + if (tracker) { + print_verbose(" - " + tracker->name); + } + } + } + } + */ + return true; } @@ -1790,14 +1932,29 @@ void OpenXRAPI::action_set_free(RID p_action_set) { action_set_owner.free(p_action_set); } -RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_toplevel_paths) { +RID OpenXRAPI::get_action_rid(XrAction p_action) { + List<RID> current; + action_owner.get_owned_list(¤t); + for (int i = 0; i < current.size(); i++) { + Action *action = action_owner.get_or_null(current[i]); + if (action && action->handle == p_action) { + return current[i]; + } + } + + return RID(); +} + +RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_trackers) { ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); Action action; + action.name = p_name; ActionSet *action_set = action_set_owner.get_or_null(p_action_set); ERR_FAIL_NULL_V(action_set, RID()); ERR_FAIL_COND_V(action_set->handle == XR_NULL_HANDLE, RID()); + action.action_set_rid = p_action_set; switch (p_action_type) { case OpenXRAction::OPENXR_ACTION_BOOL: @@ -1821,17 +1978,17 @@ RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String } Vector<XrPath> toplevel_paths; - for (int i = 0; i < p_toplevel_paths.size(); i++) { - Path *xr_path = xr_path_owner.get_or_null(p_toplevel_paths[i]); - if (xr_path != nullptr && xr_path->path != XR_NULL_PATH) { - PathWithSpace path_with_space = { - xr_path->path, // toplevel_path + for (int i = 0; i < p_trackers.size(); i++) { + Tracker *tracker = tracker_owner.get_or_null(p_trackers[i]); + if (tracker != nullptr && tracker->toplevel_path != XR_NULL_PATH) { + ActionTracker action_tracker = { + p_trackers[i], // tracker XR_NULL_HANDLE, // space false // was_location_valid }; - action.toplevel_paths.push_back(path_with_space); + action.trackers.push_back(action_tracker); - toplevel_paths.push_back(xr_path->path); + toplevel_paths.push_back(tracker->toplevel_path); } } @@ -1848,7 +2005,7 @@ RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String copy_string_to_char_buffer(p_name, action_info.actionName, XR_MAX_ACTION_NAME_SIZE); copy_string_to_char_buffer(p_localized_name, action_info.localizedActionName, XR_MAX_LOCALIZED_ACTION_NAME_SIZE); - print_line("Creating action ", action_info.actionName, action_info.localizedActionName, action_info.countSubactionPaths); + // print_line("Creating action ", action_info.actionName, action_info.localizedActionName, action_info.countSubactionPaths); XrResult result = xrCreateAction(action_set->handle, &action_info, &action.handle); if (XR_FAILED(result)) { @@ -1859,6 +2016,17 @@ RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String return action_owner.make_rid(action); } +String OpenXRAPI::action_get_name(RID p_action) { + if (p_action.is_null()) { + return String("None"); + } + + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, String()); + + return action->name; +} + void OpenXRAPI::action_free(RID p_action) { Action *action = action_owner.get_or_null(p_action); ERR_FAIL_NULL(action); @@ -1870,55 +2038,139 @@ void OpenXRAPI::action_free(RID p_action) { action_owner.free(p_action); } -bool OpenXRAPI::suggest_bindings(const String p_interaction_profile, const Vector<Binding> p_bindings) { - ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); +RID OpenXRAPI::get_interaction_profile_rid(XrPath p_path) { + List<RID> current; + interaction_profile_owner.get_owned_list(¤t); + for (int i = 0; i < current.size(); i++) { + InteractionProfile *ip = interaction_profile_owner.get_or_null(current[i]); + if (ip && ip->path == p_path) { + return current[i]; + } + } - XrPath interaction_profile; - Vector<XrActionSuggestedBinding> bindings; + return RID(); +} + +XrPath OpenXRAPI::get_interaction_profile_path(RID p_interaction_profile) { + if (p_interaction_profile.is_null()) { + return XR_NULL_PATH; + } - XrResult result = xrStringToPath(instance, p_interaction_profile.utf8().get_data(), &interaction_profile); + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL_V(ip, XR_NULL_PATH); + + return ip->path; +} + +RID OpenXRAPI::interaction_profile_create(const String p_name) { + InteractionProfile new_interaction_profile; + + XrResult result = xrStringToPath(instance, p_name.utf8().get_data(), &new_interaction_profile.path); if (XR_FAILED(result)) { - print_line("OpenXR: failed to get path for ", p_interaction_profile, "! [", get_error_string(result), "]"); - return false; + print_line("OpenXR: failed to get path for ", p_name, "! [", get_error_string(result), "]"); + return RID(); } - for (int i = 0; i < p_bindings.size(); i++) { - XrActionSuggestedBinding binding; + RID existing_ip = get_interaction_profile_rid(new_interaction_profile.path); + if (existing_ip.is_valid()) { + return existing_ip; + } - Action *action = action_owner.get_or_null(p_bindings[i].action); - if (action == nullptr || action->handle == XR_NULL_HANDLE) { - // just skip it - continue; - } + new_interaction_profile.name = p_name; + return interaction_profile_owner.make_rid(new_interaction_profile); +} - binding.action = action->handle; +String OpenXRAPI::interaction_profile_get_name(RID p_interaction_profile) { + if (p_interaction_profile.is_null()) { + return String("None"); + } - result = xrStringToPath(instance, p_bindings[i].path.utf8().get_data(), &binding.binding); - if (XR_FAILED(result)) { - print_line("OpenXR: failed to get path for ", p_bindings[i].path, "! [", get_error_string(result), "]"); - continue; - } + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL_V(ip, String()); + + return ip->name; +} + +void OpenXRAPI::interaction_profile_clear_bindings(RID p_interaction_profile) { + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL(ip); - bindings.push_back(binding); + ip->bindings.clear(); +} + +bool OpenXRAPI::interaction_profile_add_binding(RID p_interaction_profile, RID p_action, const String p_path) { + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL_V(ip, false); + + XrActionSuggestedBinding binding; + + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_COND_V(action == nullptr || action->handle == XR_NULL_HANDLE, false); + + binding.action = action->handle; + + XrResult result = xrStringToPath(instance, p_path.utf8().get_data(), &binding.binding); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to get path for ", p_path, "! [", get_error_string(result), "]"); + return false; } + ip->bindings.push_back(binding); + + 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); + const XrInteractionProfileSuggestedBinding suggested_bindings = { XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING, // type nullptr, // next - interaction_profile, // interactionProfile - uint32_t(bindings.size()), // countSuggestedBindings - bindings.ptr() // suggestedBindings + ip->path, // interactionProfile + uint32_t(ip->bindings.size()), // countSuggestedBindings + ip->bindings.ptr() // suggestedBindings }; - result = xrSuggestInteractionProfileBindings(instance, &suggested_bindings); - if (XR_FAILED(result)) { - print_line("OpenXR: failed to suggest bindings for ", p_interaction_profile, "! [", get_error_string(result), "]"); + XrResult result = xrSuggestInteractionProfileBindings(instance, &suggested_bindings); + if (result == XR_ERROR_PATH_UNSUPPORTED) { + // this is fine, not all runtimes support all devices. + print_verbose("OpenXR Interaction profile " + ip->name + " is not supported on this runtime"); + } else if (XR_FAILED(result)) { + print_line("OpenXR: failed to suggest bindings for ", ip->name, "! [", get_error_string(result), "]"); // reporting is enough... } + /* For debugging: + print_verbose("Suggested bindings for " + ip->name); + for (int i = 0; i < ip->bindings.size(); i++) { + uint32_t strlen; + char path[XR_MAX_PATH_LENGTH]; + + String action_name = action_get_name(get_action_rid(ip->bindings[i].action)); + + XrResult result = xrPathToString(instance, ip->bindings[i].binding, XR_MAX_PATH_LENGTH, &strlen, path); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to retrieve bindings for ", action_name, "! [", get_error_string(result), "]"); + } + print_verbose(" - " + action_name + " => " + String(path)); + } + */ + return true; } +void OpenXRAPI::interaction_profile_free(RID p_interaction_profile) { + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL(ip); + + ip->bindings.clear(); + + interaction_profile_owner.free(p_interaction_profile); +} + bool OpenXRAPI::sync_action_sets(const Vector<RID> p_active_sets) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); @@ -1955,12 +2207,12 @@ bool OpenXRAPI::sync_action_sets(const Vector<RID> p_active_sets) { return true; } -bool OpenXRAPI::get_action_bool(RID p_action, RID p_path) { +bool OpenXRAPI::get_action_bool(RID p_action, RID p_tracker) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); Action *action = action_owner.get_or_null(p_action); ERR_FAIL_NULL_V(action, false); - Path *path = xr_path_owner.get_or_null(p_path); - ERR_FAIL_NULL_V(path, false); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, false); if (!running) { return false; @@ -1972,7 +2224,7 @@ bool OpenXRAPI::get_action_bool(RID p_action, RID p_path) { XR_TYPE_ACTION_STATE_GET_INFO, // type nullptr, // next action->handle, // action - path->path // subactionPath + tracker->toplevel_path // subactionPath }; XrActionStateBoolean result_state; @@ -1987,12 +2239,12 @@ bool OpenXRAPI::get_action_bool(RID p_action, RID p_path) { return result_state.isActive && result_state.currentState; } -float OpenXRAPI::get_action_float(RID p_action, RID p_path) { +float OpenXRAPI::get_action_float(RID p_action, RID p_tracker) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, 0.0); Action *action = action_owner.get_or_null(p_action); ERR_FAIL_NULL_V(action, 0.0); - Path *path = xr_path_owner.get_or_null(p_path); - ERR_FAIL_NULL_V(path, 0.0); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, 0.0); if (!running) { return 0.0; @@ -2004,7 +2256,7 @@ float OpenXRAPI::get_action_float(RID p_action, RID p_path) { XR_TYPE_ACTION_STATE_GET_INFO, // type nullptr, // next action->handle, // action - path->path // subactionPath + tracker->toplevel_path // subactionPath }; XrActionStateFloat result_state; @@ -2019,12 +2271,12 @@ float OpenXRAPI::get_action_float(RID p_action, RID p_path) { return result_state.isActive ? result_state.currentState : 0.0; } -Vector2 OpenXRAPI::get_action_vector2(RID p_action, RID p_path) { +Vector2 OpenXRAPI::get_action_vector2(RID p_action, RID p_tracker) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, Vector2()); Action *action = action_owner.get_or_null(p_action); ERR_FAIL_NULL_V(action, Vector2()); - Path *path = xr_path_owner.get_or_null(p_path); - ERR_FAIL_NULL_V(path, Vector2()); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, Vector2()); if (!running) { return Vector2(); @@ -2036,7 +2288,7 @@ Vector2 OpenXRAPI::get_action_vector2(RID p_action, RID p_path) { XR_TYPE_ACTION_STATE_GET_INFO, // type nullptr, // next action->handle, // action - path->path // subactionPath + tracker->toplevel_path // subactionPath }; XrActionStateVector2f result_state; @@ -2051,12 +2303,12 @@ Vector2 OpenXRAPI::get_action_vector2(RID p_action, RID p_path) { return result_state.isActive ? Vector2(result_state.currentState.x, result_state.currentState.y) : Vector2(); } -XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) { +XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_tracker, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, XRPose::XR_TRACKING_CONFIDENCE_NONE); Action *action = action_owner.get_or_null(p_action); ERR_FAIL_NULL_V(action, XRPose::XR_TRACKING_CONFIDENCE_NONE); - Path *path = xr_path_owner.get_or_null(p_path); - ERR_FAIL_NULL_V(path, XRPose::XR_TRACKING_CONFIDENCE_NONE); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, XRPose::XR_TRACKING_CONFIDENCE_NONE); if (!running) { return XRPose::XR_TRACKING_CONFIDENCE_NONE; @@ -2064,10 +2316,12 @@ XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_POSE_INPUT, XRPose::XR_TRACKING_CONFIDENCE_NONE); + // print_verbose("Checking " + action->name + " => " + tracker->name + " (" + itos(tracker->toplevel_path) + ")"); + uint64_t index = 0xFFFFFFFF; - uint64_t size = uint64_t(action->toplevel_paths.size()); + uint64_t size = uint64_t(action->trackers.size()); for (uint64_t i = 0; i < size && index == 0xFFFFFFFF; i++) { - if (action->toplevel_paths[i].toplevel_path == path->path) { + if (action->trackers[i].tracker_rid == p_tracker) { index = i; } } @@ -2077,14 +2331,19 @@ XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, return XRPose::XR_TRACKING_CONFIDENCE_NONE; } - if (action->toplevel_paths[index].space == XR_NULL_HANDLE) { + XrTime display_time = get_next_frame_time(); + if (display_time == 0) { + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + if (action->trackers[index].space == XR_NULL_HANDLE) { // if this is a pose we need to define spaces XrActionSpaceCreateInfo action_space_info = { XR_TYPE_ACTION_SPACE_CREATE_INFO, // type nullptr, // next action->handle, // action - action->toplevel_paths[index].toplevel_path, // subactionPath + tracker->toplevel_path, // subactionPath { { 0.0, 0.0, 0.0, 1.0 }, // orientation { 0.0, 0.0, 0.0 } // position @@ -2098,11 +2357,9 @@ XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, return XRPose::XR_TRACKING_CONFIDENCE_NONE; } - action->toplevel_paths.ptrw()[index].space = space; + action->trackers.ptrw()[index].space = space; } - XrTime display_time = get_next_frame_time(); - XrSpaceVelocity velocity = { XR_TYPE_SPACE_VELOCITY, // type nullptr, // next @@ -2121,7 +2378,7 @@ XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, } // pose }; - XrResult result = xrLocateSpace(action->toplevel_paths[index].space, play_space, display_time, &location); + XrResult result = xrLocateSpace(action->trackers[index].space, play_space, display_time, &location); if (XR_FAILED(result)) { print_line("OpenXR: failed to locate space! [", get_error_string(result), "]"); return XRPose::XR_TRACKING_CONFIDENCE_NONE; @@ -2133,12 +2390,12 @@ XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, return confidence; } -bool OpenXRAPI::trigger_haptic_pulse(RID p_action, RID p_path, float p_frequency, float p_amplitude, XrDuration p_duration_ns) { +bool OpenXRAPI::trigger_haptic_pulse(RID p_action, RID p_tracker, float p_frequency, float p_amplitude, XrDuration p_duration_ns) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); Action *action = action_owner.get_or_null(p_action); ERR_FAIL_NULL_V(action, false); - Path *path = xr_path_owner.get_or_null(p_path); - ERR_FAIL_NULL_V(path, false); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, false); if (!running) { return false; @@ -2150,7 +2407,7 @@ bool OpenXRAPI::trigger_haptic_pulse(RID p_action, RID p_path, float p_frequency XR_TYPE_HAPTIC_ACTION_INFO, // type nullptr, // next action->handle, // action - path->path // subactionPath + tracker->toplevel_path // subactionPath }; XrHapticVibration vibration = { diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 33b503543a..dc224c4237 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -28,16 +28,16 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef OPENXR_DRIVER_H -#define OPENXR_DRIVER_H +#ifndef OPENXR_API_H +#define OPENXR_API_H #include "core/error/error_macros.h" -#include "core/math/camera_matrix.h" +#include "core/math/projection.h" #include "core/math/transform_3d.h" #include "core/math/vector2.h" #include "core/os/memory.h" #include "core/string/ustring.h" -#include "core/templates/map.h" +#include "core/templates/rb_map.h" #include "core/templates/rid_owner.h" #include "core/templates/vector.h" #include "servers/xr/xr_pose.h" @@ -55,12 +55,16 @@ // forward declarations, we don't want to include these fully class OpenXRVulkanExtension; +class OpenXRInterface; class OpenXRAPI { private: // our singleton static OpenXRAPI *singleton; + // linked XR interface + OpenXRInterface *xr_interface = nullptr; + // layers uint32_t num_layer_properties = 0; XrApiLayerProperties *layer_properties = nullptr; @@ -69,7 +73,13 @@ private: uint32_t num_supported_extensions = 0; XrExtensionProperties *supported_extensions = nullptr; Vector<OpenXRExtensionWrapper *> registered_extension_wrappers; - Vector<const char *> enabled_extensions; + Vector<CharString> enabled_extensions; + + bool ext_hp_mixed_reality_available = false; + bool ext_samsung_odyssey_available = false; + bool ext_vive_cosmos_available = false; + bool ext_vive_focus3_available = false; + bool ext_huawei_controller_available = false; // composition layer providers Vector<OpenXRCompositionLayerProvider *> composition_layer_providers; @@ -94,9 +104,9 @@ private: // state XrInstance instance = XR_NULL_HANDLE; - XrSystemId system_id; + XrSystemId system_id = 0; String system_name; - uint32_t vendor_id; + uint32_t vendor_id = 0; XrSystemTrackingProperties tracking_properties; XrSession session = XR_NULL_HANDLE; XrSessionState session_state = XR_SESSION_STATE_UNKNOWN; @@ -122,7 +132,7 @@ private: bool load_layer_properties(); bool load_supported_extensions(); - bool is_extension_supported(const char *p_extension) const; + bool is_extension_supported(const String &p_extension) const; // instance bool create_instance(); @@ -148,29 +158,45 @@ private: bool release_image(XrSwapchain p_swapchain); // action map - struct Path { - XrPath path; + struct Tracker { // Trackers represent tracked physical objects such as controllers, pucks, etc. + String name; // Name for this tracker (i.e. "/user/hand/left") + XrPath toplevel_path; // OpenXR XrPath for this tracker + RID active_profile_rid; // RID of the active profile for this tracker }; - RID_Owner<Path, true> xr_path_owner; + RID_Owner<Tracker, true> tracker_owner; + RID get_tracker_rid(XrPath p_path); - struct ActionSet { - bool is_attached; - XrActionSet handle; + struct ActionSet { // Action sets define a set of actions that can be enabled together + String name; // Name for this action set (i.e. "godot_action_set") + bool is_attached; // If true our action set has been attached to the session and can no longer be modified + XrActionSet handle; // OpenXR handle for this action set }; RID_Owner<ActionSet, true> action_set_owner; - struct PathWithSpace { - XrPath toplevel_path; - XrSpace space; - bool was_location_valid; + struct ActionTracker { // Links and action to a tracker + RID tracker_rid; // RID of the tracker + XrSpace space; // Optional space for pose actions + bool was_location_valid; // If true the last position we obtained was valid }; - struct Action { - XrActionType action_type; - Vector<PathWithSpace> toplevel_paths; - XrAction handle; + struct Action { // Actions define the inputs and outputs in OpenXR + RID action_set_rid; // RID of the action set this action belongs to + String name; // Name for this action (i.e. "aim_pose") + XrActionType action_type; // Type of action (bool, float, etc.) + Vector<ActionTracker> trackers; // The trackers this action can be used with + XrAction handle; // OpenXR handle for this action }; RID_Owner<Action, true> action_owner; + RID get_action_rid(XrAction p_action); + + struct InteractionProfile { // Interaction profiles define suggested bindings between the physical inputs on controller types and our actions + String name; // Name of the interaction profile (i.e. "/interaction_profiles/valve/index_controller") + XrPath path; // OpenXR path for this profile + Vector<XrActionSuggestedBinding> bindings; // OpenXR action bindings + }; + RID_Owner<InteractionProfile, true> interaction_profile_owner; + RID get_interaction_profile_rid(XrPath p_path); + XrPath get_interaction_profile_path(RID p_interaction_profile); // state changes bool poll_events(); @@ -199,22 +225,22 @@ protected: // helper method to get a valid Transform3D from an openxr space location 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); + void parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); public: - static void setup_global_defs(); - static bool openxr_is_enabled(); + static bool openxr_is_enabled(bool p_check_run_in_editor = true); static OpenXRAPI *get_singleton(); String get_error_string(XrResult result); String get_swapchain_format_name(int64_t p_swapchain_format) const; + void set_xr_interface(OpenXRInterface *p_xr_interface); void register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper); bool is_initialized(); bool is_running(); - bool initialise(const String &p_rendering_driver); - bool initialise_session(); + bool initialize(const String &p_rendering_driver); + bool initialize_session(); void finish(); XrTime get_next_frame_time() { return frame_state.predictedDisplayTime + frame_state.predictedDisplayPeriod; }; @@ -223,7 +249,7 @@ public: Size2 get_recommended_target_size(); XRPose::TrackingConfidence get_head_center(Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); bool get_view_transform(uint32_t p_view, Transform3D &r_transform); - bool get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, CameraMatrix &p_camera_matrix); + bool get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, Projection &p_camera_matrix); bool process(); void pre_render(); @@ -233,29 +259,37 @@ public: // action map String get_default_action_map_resource_name(); - RID path_create(const String p_name); - void path_free(RID p_path); + + RID tracker_create(const String p_name); + String tracker_get_name(RID p_tracker); + void tracker_check_profile(RID p_tracker, XrSession p_session = XR_NULL_HANDLE); + void tracker_free(RID p_tracker); + 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); bool action_set_attach(RID p_action_set); 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<RID> &p_toplevel_paths); + + RID action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_trackers); + String action_get_name(RID p_action); void action_free(RID p_action); - struct Binding { - RID action; - String path; - }; - bool suggest_bindings(const String p_interaction_profile, const Vector<Binding> p_bindings); + 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_suggest_bindings(RID p_interaction_profile); + void interaction_profile_free(RID p_interaction_profile); bool sync_action_sets(const Vector<RID> p_active_sets); - bool get_action_bool(RID p_action, RID p_path); - float get_action_float(RID p_action, RID p_path); - Vector2 get_action_vector2(RID p_action, RID p_path); - XRPose::TrackingConfidence get_action_pose(RID p_action, RID p_path, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); - bool trigger_haptic_pulse(RID p_action, RID p_path, float p_frequency, float p_amplitude, XrDuration p_duration_ns); + bool get_action_bool(RID p_action, RID p_tracker); + float get_action_float(RID p_action, RID p_tracker); + Vector2 get_action_vector2(RID p_action, RID p_tracker); + XRPose::TrackingConfidence get_action_pose(RID p_action, RID p_tracker, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); + bool trigger_haptic_pulse(RID p_action, RID p_tracker, float p_frequency, float p_amplitude, XrDuration p_duration_ns); OpenXRAPI(); ~OpenXRAPI(); }; -#endif // !OPENXR_DRIVER_H +#endif // OPENXR_API_H diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index 394f634687..6c2f08e21d 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -35,7 +35,12 @@ #include "servers/rendering/rendering_server_globals.h" void OpenXRInterface::_bind_methods() { - // todo + // lifecycle signals + ADD_SIGNAL(MethodInfo("session_begun")); + ADD_SIGNAL(MethodInfo("session_stopping")); + ADD_SIGNAL(MethodInfo("session_focussed")); + ADD_SIGNAL(MethodInfo("session_visible")); + ADD_SIGNAL(MethodInfo("pose_recentered")); } StringName OpenXRInterface::get_name() const { @@ -46,6 +51,35 @@ uint32_t OpenXRInterface::get_capabilities() const { return XRInterface::XR_VR + XRInterface::XR_STEREO; }; +PackedStringArray OpenXRInterface::get_suggested_tracker_names() const { + // These are hardcoded in OpenXR, note that they will only be available if added to our action map + + PackedStringArray arr = { + "left_hand", // /user/hand/left is mapped to our defaults + "right_hand", // /user/hand/right is mapped to our defaults + "/user/treadmill", + + // Even though these are only available if you have the tracker extension, + // we add these as we may be deploying on a different platform than our + // editor is running on. + "/user/vive_tracker_htcx/role/handheld_object", + "/user/vive_tracker_htcx/role/left_foot", + "/user/vive_tracker_htcx/role/right_foot", + "/user/vive_tracker_htcx/role/left_shoulder", + "/user/vive_tracker_htcx/role/right_shoulder", + "/user/vive_tracker_htcx/role/left_elbow", + "/user/vive_tracker_htcx/role/right_elbow", + "/user/vive_tracker_htcx/role/left_knee", + "/user/vive_tracker_htcx/role/right_knee", + "/user/vive_tracker_htcx/role/waist", + "/user/vive_tracker_htcx/role/chest", + "/user/vive_tracker_htcx/role/camera", + "/user/vive_tracker_htcx/role/keyboard" + }; + + return arr; +} + XRInterface::TrackingStatus OpenXRInterface::get_tracking_status() const { return tracking_state; } @@ -64,8 +98,9 @@ void OpenXRInterface::_load_action_map() { // This allow us to process the relevant actions each frame. // just in case clean up - free_action_sets(); free_trackers(); + free_interaction_profiles(); + free_action_sets(); Ref<OpenXRActionMap> action_map; if (Engine::get_singleton()->is_editor_hint()) { @@ -88,14 +123,14 @@ void OpenXRInterface::_load_action_map() { #ifdef TOOLS_ENABLED // Save our action sets so our user can action_map->set_path(default_tres_name, true); - ResourceSaver::save(default_tres_name, action_map); + ResourceSaver::save(action_map, default_tres_name); #endif } } // process our action map if (action_map.is_valid()) { - Map<Ref<OpenXRAction>, RID> action_rids; + HashMap<Ref<OpenXRAction>, Action *> xr_actions; Array action_sets = action_map->get_action_sets(); for (int i = 0; i < action_sets.size(); i++) { @@ -112,18 +147,16 @@ void OpenXRInterface::_load_action_map() { Ref<OpenXRAction> xr_action = actions[j]; PackedStringArray toplevel_paths = xr_action->get_toplevel_paths(); - Vector<RID> toplevel_rids; Vector<Tracker *> trackers; for (int k = 0; k < toplevel_paths.size(); k++) { - Tracker *tracker = get_tracker(toplevel_paths[k]); + Tracker *tracker = find_tracker(toplevel_paths[k], true); if (tracker) { - toplevel_rids.push_back(tracker->path_rid); trackers.push_back(tracker); } } - Action *action = create_action(action_set, xr_action->get_name(), xr_action->get_localized_name(), xr_action->get_action_type(), toplevel_rids); + Action *action = create_action(action_set, xr_action->get_name(), xr_action->get_localized_name(), xr_action->get_action_type(), trackers); if (action) { // we link our actions back to our trackers so we know which actions to check when we're processing our trackers for (int t = 0; t < trackers.size(); t++) { @@ -131,7 +164,7 @@ void OpenXRInterface::_load_action_map() { } // add this to our map for creating our interaction profiles - action_rids[xr_action] = action->action_rid; + xr_actions[xr_action] = action; } } } @@ -139,30 +172,38 @@ void OpenXRInterface::_load_action_map() { // now do our suggestions Array interaction_profiles = action_map->get_interaction_profiles(); for (int i = 0; i < interaction_profiles.size(); i++) { - Vector<OpenXRAPI::Binding> bindings; Ref<OpenXRInteractionProfile> xr_interaction_profile = interaction_profiles[i]; + // Note, we can only have one entry per interaction profile so if it already exists we clear it out + RID ip = openxr_api->interaction_profile_create(xr_interaction_profile->get_interaction_profile_path()); + openxr_api->interaction_profile_clear_bindings(ip); + Array xr_bindings = xr_interaction_profile->get_bindings(); for (int j = 0; j < xr_bindings.size(); j++) { Ref<OpenXRIPBinding> xr_binding = xr_bindings[j]; Ref<OpenXRAction> xr_action = xr_binding->get_action(); - OpenXRAPI::Binding binding; - if (action_rids.has(xr_action)) { - binding.action = action_rids[xr_action]; + Action *action = nullptr; + if (xr_actions.has(xr_action)) { + action = xr_actions[xr_action]; } else { print_line("Action ", xr_action->get_name(), " isn't part of an action set!"); continue; } - PackedStringArray xr_paths = xr_binding->get_paths(); - for (int k = 0; k < xr_paths.size(); k++) { - binding.path = xr_paths[k]; - bindings.push_back(binding); + PackedStringArray paths = xr_binding->get_paths(); + for (int k = 0; k < paths.size(); k++) { + openxr_api->interaction_profile_add_binding(ip, action->action_rid, paths[k]); } } - openxr_api->suggest_bindings(xr_interaction_profile->get_interaction_profile_path(), bindings); + // Now submit our suggestions + openxr_api->interaction_profile_suggest_bindings(ip); + + // And record it in our array so we can clean it up later on + if (interaction_profiles.has(ip)) { + interaction_profiles.push_back(ip); + } } } } @@ -193,15 +234,16 @@ void OpenXRInterface::free_action_sets() { for (int i = 0; i < action_sets.size(); i++) { ActionSet *action_set = action_sets[i]; - openxr_api->path_free(action_set->action_set_rid); free_actions(action_set); + openxr_api->action_set_free(action_set->action_set_rid); + memfree(action_set); } action_sets.clear(); } -OpenXRInterface::Action *OpenXRInterface::create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> p_toplevel_paths) { +OpenXRInterface::Action *OpenXRInterface::create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<Tracker *> p_trackers) { ERR_FAIL_NULL_V(openxr_api, nullptr); for (int i = 0; i < p_action_set->actions.size(); i++) { @@ -211,10 +253,31 @@ OpenXRInterface::Action *OpenXRInterface::create_action(ActionSet *p_action_set, } } + Vector<RID> tracker_rids; + for (int i = 0; i < p_trackers.size(); i++) { + tracker_rids.push_back(p_trackers[i]->tracker_rid); + } + Action *action = memnew(Action); - action->action_name = p_action_name; + if (p_action_type == OpenXRAction::OPENXR_ACTION_POSE) { + // We can't have dual action names in OpenXR hence we added _pose, + // but default, aim and grip and default pose action names in Godot so rename them on the tracker. + // NOTE need to decide on whether we should keep the naming convention or rename it on Godots side + if (p_action_name == "default_pose") { + action->action_name = "default"; + } else if (p_action_name == "aim_pose") { + action->action_name = "aim"; + } else if (p_action_name == "grip_pose") { + action->action_name = "grip"; + } else { + action->action_name = p_action_name; + } + } else { + action->action_name = p_action_name; + } + action->action_type = p_action_type; - action->action_rid = openxr_api->action_create(p_action_set->action_set_rid, p_action_name, p_localized_name, p_action_type, p_toplevel_paths); + action->action_rid = openxr_api->action_create(p_action_set->action_set_rid, p_action_name, p_localized_name, p_action_type, tracker_rids); p_action_set->actions.push_back(action); return action; @@ -248,7 +311,7 @@ void OpenXRInterface::free_actions(ActionSet *p_action_set) { p_action_set->actions.clear(); } -OpenXRInterface::Tracker *OpenXRInterface::get_tracker(const String &p_path_name) { +OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_tracker_name, bool p_create) { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL_V(xr_server, nullptr); ERR_FAIL_NULL_V(openxr_api, nullptr); @@ -256,52 +319,72 @@ OpenXRInterface::Tracker *OpenXRInterface::get_tracker(const String &p_path_name Tracker *tracker = nullptr; for (int i = 0; i < trackers.size(); i++) { tracker = trackers[i]; - if (tracker->path_name == p_path_name) { + if (tracker->tracker_name == p_tracker_name) { return tracker; } } + if (!p_create) { + return nullptr; + } + + // Create our RID + RID tracker_rid = openxr_api->tracker_create(p_tracker_name); + ERR_FAIL_COND_V(tracker_rid.is_null(), nullptr); + // create our positional tracker Ref<XRPositionalTracker> positional_tracker; positional_tracker.instantiate(); // We have standardised some names to make things nicer to the user so lets recognise the toplevel paths related to these. - if (p_path_name == "/user/hand/left") { + if (p_tracker_name == "/user/hand/left") { positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); positional_tracker->set_tracker_name("left_hand"); positional_tracker->set_tracker_desc("Left hand controller"); positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT); - } else if (p_path_name == "/user/hand/right") { + } else if (p_tracker_name == "/user/hand/right") { positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); positional_tracker->set_tracker_name("right_hand"); positional_tracker->set_tracker_desc("Right hand controller"); positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT); } else { positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); - positional_tracker->set_tracker_name(p_path_name); - positional_tracker->set_tracker_desc(p_path_name); + positional_tracker->set_tracker_name(p_tracker_name); + positional_tracker->set_tracker_desc(p_tracker_name); } + positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); xr_server->add_tracker(positional_tracker); // create a new entry tracker = memnew(Tracker); - tracker->path_name = p_path_name; - tracker->path_rid = openxr_api->path_create(p_path_name); + tracker->tracker_name = p_tracker_name; + tracker->tracker_rid = tracker_rid; tracker->positional_tracker = positional_tracker; + tracker->interaction_profile = RID(); trackers.push_back(tracker); return tracker; } -OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_positional_tracker_name) { - for (int i = 0; i < trackers.size(); i++) { - Tracker *tracker = trackers[i]; - if (tracker->positional_tracker.is_valid() && tracker->positional_tracker->get_tracker_name() == p_positional_tracker_name) { - return tracker; +void OpenXRInterface::tracker_profile_changed(RID p_tracker, RID p_interaction_profile) { + Tracker *tracker = nullptr; + for (int i = 0; i < trackers.size() && tracker == nullptr; i++) { + if (trackers[i]->tracker_rid == p_tracker) { + tracker = trackers[i]; } } + ERR_FAIL_NULL(tracker); - return nullptr; + tracker->interaction_profile = p_interaction_profile; + + if (p_interaction_profile.is_null()) { + print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + INTERACTION_PROFILE_NONE); + tracker->positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); + } else { + String name = openxr_api->interaction_profile_get_name(p_interaction_profile); + print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + name); + tracker->positional_tracker->set_tracker_profile(name); + } } void OpenXRInterface::link_action_to_tracker(Tracker *p_tracker, Action *p_action) { @@ -314,40 +397,43 @@ void OpenXRInterface::handle_tracker(Tracker *p_tracker) { ERR_FAIL_NULL(openxr_api); ERR_FAIL_COND(p_tracker->positional_tracker.is_null()); - // handle all the actions + // Note, which actions are actually bound to inputs are handled by our interaction profiles however interaction + // profiles are suggested bindings for controller types we know about. OpenXR runtimes can stray away from these + // and rebind them or even offer bindings to controllers that are not known to us. + + // We don't really have a consistent way to detect whether a controller is active however as long as it is + // unbound it seems to be unavailable, so far unknown controller seem to mimic one of the profiles we've + // supplied. + if (p_tracker->interaction_profile.is_null()) { + return; + } + + // We check all actions that are related to our tracker. for (int i = 0; i < p_tracker->actions.size(); i++) { Action *action = p_tracker->actions[i]; switch (action->action_type) { case OpenXRAction::OPENXR_ACTION_BOOL: { - bool pressed = openxr_api->get_action_bool(action->action_rid, p_tracker->path_rid); + bool pressed = openxr_api->get_action_bool(action->action_rid, p_tracker->tracker_rid); p_tracker->positional_tracker->set_input(action->action_name, Variant(pressed)); } break; case OpenXRAction::OPENXR_ACTION_FLOAT: { - real_t value = openxr_api->get_action_float(action->action_rid, p_tracker->path_rid); + real_t value = openxr_api->get_action_float(action->action_rid, p_tracker->tracker_rid); p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); } break; case OpenXRAction::OPENXR_ACTION_VECTOR2: { - Vector2 value = openxr_api->get_action_vector2(action->action_rid, p_tracker->path_rid); + Vector2 value = openxr_api->get_action_vector2(action->action_rid, p_tracker->tracker_rid); p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); } break; case OpenXRAction::OPENXR_ACTION_POSE: { Transform3D transform; Vector3 linear, angular; - XRPose::TrackingConfidence confidence = openxr_api->get_action_pose(action->action_rid, p_tracker->path_rid, transform, linear, angular); + + XRPose::TrackingConfidence confidence = openxr_api->get_action_pose(action->action_rid, p_tracker->tracker_rid, transform, linear, angular); + if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { - String name; - // We can't have dual action names in OpenXR hence we added _pose, but default, aim and grip and default pose action names in Godot so rename them on the tracker. - // NOTE need to decide on whether we should keep the naming convention or rename it on Godots side - if (action->action_name == "default_pose") { - name = "default"; - } else if (action->action_name == "aim_pose") { - name = "aim"; - } else if (action->action_name == "grip_pose") { - name = "grip"; - } else { - name = action->action_name; - } - p_tracker->positional_tracker->set_pose(name, transform, linear, angular, confidence); + p_tracker->positional_tracker->set_pose(action->action_name, transform, linear, angular, confidence); + } else { + p_tracker->positional_tracker->invalidate_pose(action->action_name); } } break; default: { @@ -368,7 +454,7 @@ void OpenXRInterface::trigger_haptic_pulse(const String &p_action_name, const St XrDuration duration = XrDuration(p_duration_sec * 1000000000.0); // seconds -> nanoseconds - openxr_api->trigger_haptic_pulse(action->action_rid, tracker->path_rid, p_frequency, p_amplitude, duration); + openxr_api->trigger_haptic_pulse(action->action_rid, tracker->tracker_rid, p_frequency, p_amplitude, duration); } void OpenXRInterface::free_trackers() { @@ -379,7 +465,7 @@ void OpenXRInterface::free_trackers() { for (int i = 0; i < trackers.size(); i++) { Tracker *tracker = trackers[i]; - openxr_api->path_free(tracker->path_rid); + openxr_api->tracker_free(tracker->tracker_rid); xr_server->remove_tracker(tracker->positional_tracker); tracker->positional_tracker.unref(); @@ -388,7 +474,16 @@ void OpenXRInterface::free_trackers() { trackers.clear(); } -bool OpenXRInterface::initialise_on_startup() const { +void OpenXRInterface::free_interaction_profiles() { + ERR_FAIL_NULL(openxr_api); + + for (int i = 0; i < interaction_profiles.size(); i++) { + openxr_api->interaction_profile_free(interaction_profiles[i]); + } + interaction_profiles.clear(); +} + +bool OpenXRInterface::initialize_on_startup() const { if (openxr_api == nullptr) { return false; } else if (!openxr_api->is_initialized()) { @@ -417,7 +512,7 @@ bool OpenXRInterface::initialize() { // load up our action sets before setting up our session, note that our profiles are suggestions, OpenXR takes ownership of (re)binding _load_action_map(); - if (!openxr_api->initialise_session()) { + if (!openxr_api->initialize_session()) { return false; } @@ -447,14 +542,14 @@ void OpenXRInterface::uninitialize() { // end the session if we need to? // cleanup stuff - free_action_sets(); free_trackers(); + free_interaction_profiles(); + free_action_sets(); XRServer *xr_server = XRServer::get_singleton(); if (xr_server) { if (head.is_valid()) { xr_server->remove_tracker(head); - head.unref(); } } @@ -536,8 +631,8 @@ Transform3D OpenXRInterface::get_transform_for_view(uint32_t p_view, const Trans return p_cam_transform * xr_server->get_reference_frame() * t; } -CameraMatrix OpenXRInterface::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) { - CameraMatrix cm; +Projection OpenXRInterface::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) { + Projection cm; if (openxr_api) { if (openxr_api->get_view_projection(p_view, p_z_near, p_z_far, cm)) { @@ -649,8 +744,31 @@ void OpenXRInterface::end_frame() { } } +void OpenXRInterface::on_state_ready() { + emit_signal(SNAME("session_begun")); +} + +void OpenXRInterface::on_state_visible() { + emit_signal(SNAME("session_visible")); +} + +void OpenXRInterface::on_state_focused() { + emit_signal(SNAME("session_focussed")); +} + +void OpenXRInterface::on_state_stopping() { + emit_signal(SNAME("session_stopping")); +} + +void OpenXRInterface::on_pose_recentered() { + emit_signal(SNAME("pose_recentered")); +} + OpenXRInterface::OpenXRInterface() { openxr_api = OpenXRAPI::get_singleton(); + if (openxr_api) { + openxr_api->set_xr_interface(this); + } // while we don't have head tracking, don't put the headset on the floor... _set_default_pos(head_transform, 1.0, 0); @@ -659,5 +777,12 @@ OpenXRInterface::OpenXRInterface() { } OpenXRInterface::~OpenXRInterface() { - openxr_api = nullptr; + if (is_initialized()) { + uninitialize(); + } + + if (openxr_api) { + openxr_api->set_xr_interface(nullptr); + openxr_api = nullptr; + } } diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index ede7d481d2..a99012fd1d 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -37,6 +37,9 @@ #include "action_map/openxr_action_map.h" #include "openxr_api.h" +// declare some default strings +#define INTERACTION_PROFILE_NONE "/interaction_profiles/none" + class OpenXRInterface : public XRInterface { GDCLASS(OpenXRInterface, XRInterface); @@ -54,40 +57,43 @@ private: void _load_action_map(); - struct Action { - String action_name; - OpenXRAction::ActionType action_type; - RID action_rid; + struct Action { // An action we've registered with OpenXR + String action_name; // Name of our action as presented to Godot (can be altered from the action map) + OpenXRAction::ActionType action_type; // The action type of this action + RID action_rid; // RID of the action registered with our OpenXR API }; - struct ActionSet { - String action_set_name; - bool is_active; - RID action_set_rid; - Vector<Action *> actions; + struct ActionSet { // An action set we've registered with OpenXR + String action_set_name; // Name of our action set + bool is_active; // If true this action set is active and we will sync it + Vector<Action *> actions; // List of actions in this action set + RID action_set_rid; // RID of the action registered with our OpenXR API }; - struct Tracker { - String path_name; - RID path_rid; - Ref<XRPositionalTracker> positional_tracker; - Vector<Action *> actions; + struct Tracker { // A tracker we've registered with OpenXR + String tracker_name; // Name of our tracker (can be altered from the action map) + Vector<Action *> actions; // Actions related to this tracker + Ref<XRPositionalTracker> positional_tracker; // Our positional tracker object that holds our tracker state + RID tracker_rid; // RID of the tracker registered with our OpenXR API + RID interaction_profile; // RID of the interaction profile bound to this tracker (can be null) }; Vector<ActionSet *> action_sets; + Vector<RID> interaction_profiles; Vector<Tracker *> trackers; ActionSet *create_action_set(const String &p_action_set_name, const String &p_localized_name, const int p_priority); void free_action_sets(); - Action *create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> p_toplevel_paths); + Action *create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<Tracker *> p_trackers); Action *find_action(const String &p_action_name); void free_actions(ActionSet *p_action_set); - Tracker *get_tracker(const String &p_path_name); - Tracker *find_tracker(const String &p_positional_tracker_name); + Tracker *find_tracker(const String &p_tracker_name, bool p_create = false); void link_action_to_tracker(Tracker *p_tracker, Action *p_action); void handle_tracker(Tracker *p_tracker); void free_trackers(); + void free_interaction_profiles(); + void _set_default_pos(Transform3D &p_transform, double p_world_scale, uint64_t p_eye); protected: @@ -97,9 +103,10 @@ public: virtual StringName get_name() const override; virtual uint32_t get_capabilities() const override; + virtual PackedStringArray get_suggested_tracker_names() const override; virtual TrackingStatus get_tracking_status() const override; - bool initialise_on_startup() const; + bool initialize_on_startup() const; virtual bool is_initialized() const override; virtual bool initialize() override; virtual void uninitialize() override; @@ -114,7 +121,7 @@ public: virtual uint32_t get_view_count() override; virtual Transform3D get_camera_transform() override; virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; - virtual CameraMatrix get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override; + virtual Projection get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override; virtual void process() override; virtual void pre_render() override; @@ -122,8 +129,15 @@ public: virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override; virtual void end_frame() override; + void on_state_ready(); + void on_state_visible(); + void on_state_focused(); + void on_state_stopping(); + void on_pose_recentered(); + void tracker_profile_changed(RID p_tracker, RID p_interaction_profile); + OpenXRInterface(); ~OpenXRInterface(); }; -#endif // !OPENXR_INTERFACE_H +#endif // OPENXR_INTERFACE_H diff --git a/modules/openxr/openxr_util.cpp b/modules/openxr/openxr_util.cpp index e515336daa..230b10c5f1 100644 --- a/modules/openxr/openxr_util.cpp +++ b/modules/openxr/openxr_util.cpp @@ -278,6 +278,20 @@ String OpenXRUtil::get_session_state_name(XrSessionState p_session_state) { } } +String OpenXRUtil::get_action_type_name(XrActionType p_action_type) { + switch (p_action_type) { + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_BOOLEAN_INPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_FLOAT_INPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_VECTOR2F_INPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_POSE_INPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_VIBRATION_OUTPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_MAX_ENUM) + default: { + return String("Action type ") + String::num_int64(int64_t(p_action_type)); + } break; + } +} + String OpenXRUtil::make_xr_version_string(XrVersion p_version) { String version; diff --git a/modules/openxr/openxr_util.h b/modules/openxr/openxr_util.h index 1261268376..a5cc7cd512 100644 --- a/modules/openxr/openxr_util.h +++ b/modules/openxr/openxr_util.h @@ -40,7 +40,8 @@ public: static String get_reference_space_name(XrReferenceSpaceType p_reference_space); static String get_structure_type_name(XrStructureType p_structure_type); static String get_session_state_name(XrSessionState p_session_state); + static String get_action_type_name(XrActionType p_action_type); static String make_xr_version_string(XrVersion p_version); }; -#endif // !OPENXR_UTIL_H +#endif // OPENXR_UTIL_H diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index 86ff368619..c765f169dc 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -38,46 +38,84 @@ #include "action_map/openxr_action_set.h" #include "action_map/openxr_interaction_profile.h" -OpenXRAPI *openxr_api = nullptr; -Ref<OpenXRInterface> openxr_interface; +#ifdef TOOLS_ENABLED -void preregister_openxr_types() { - // For now we create our openxr device here. If we merge it with openxr_interface we'll create that here soon. +#include "editor/editor_node.h" +#include "editor/openxr_editor_plugin.h" - OpenXRAPI::setup_global_defs(); - openxr_api = OpenXRAPI::get_singleton(); - if (openxr_api) { - if (!openxr_api->initialise(Main::get_rendering_driver_name())) { - return; - } +static void _editor_init() { + if (OpenXRAPI::openxr_is_enabled(false)) { + // Only add our OpenXR action map editor if OpenXR is enabled for our project + + OpenXREditorPlugin *openxr_plugin = memnew(OpenXREditorPlugin()); + EditorNode::get_singleton()->add_editor_plugin(openxr_plugin); } } -void register_openxr_types() { - GDREGISTER_CLASS(OpenXRInterface); +#endif + +static OpenXRAPI *openxr_api = nullptr; +static Ref<OpenXRInterface> openxr_interface; - GDREGISTER_CLASS(OpenXRAction); - GDREGISTER_CLASS(OpenXRActionSet); - GDREGISTER_CLASS(OpenXRActionMap); - GDREGISTER_CLASS(OpenXRIPBinding); - GDREGISTER_CLASS(OpenXRInteractionProfile); +void initialize_openxr_module(ModuleInitializationLevel p_level) { + if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) { + // For now we create our openxr device here. If we merge it with openxr_interface we'll create that here soon. - XRServer *xr_server = XRServer::get_singleton(); - if (xr_server) { - openxr_interface.instantiate(); - xr_server->add_interface(openxr_interface); + if (OpenXRAPI::openxr_is_enabled()) { + openxr_api = memnew(OpenXRAPI); + ERR_FAIL_NULL(openxr_api); - if (openxr_interface->initialise_on_startup()) { - openxr_interface->initialize(); + if (!openxr_api->initialize(Main::get_rendering_driver_name())) { + memdelete(openxr_api); + openxr_api = nullptr; + return; + } } } + + if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { + GDREGISTER_CLASS(OpenXRInterface); + + GDREGISTER_CLASS(OpenXRAction); + GDREGISTER_CLASS(OpenXRActionSet); + GDREGISTER_CLASS(OpenXRActionMap); + GDREGISTER_CLASS(OpenXRIPBinding); + GDREGISTER_CLASS(OpenXRInteractionProfile); + + XRServer *xr_server = XRServer::get_singleton(); + if (xr_server) { + openxr_interface.instantiate(); + xr_server->add_interface(openxr_interface); + + if (openxr_interface->initialize_on_startup()) { + openxr_interface->initialize(); + } + } + +#ifdef TOOLS_ENABLED + EditorNode::add_init_callback(_editor_init); +#endif + } } -void unregister_openxr_types() { +void uninitialize_openxr_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + if (openxr_interface.is_valid()) { + // uninitialize just in case + if (openxr_interface->is_initialized()) { + openxr_interface->uninitialize(); + } + // unregister our interface from the XR server - if (XRServer::get_singleton()) { - XRServer::get_singleton()->remove_interface(openxr_interface); + XRServer *xr_server = XRServer::get_singleton(); + if (xr_server) { + if (xr_server->get_primary_interface() == openxr_interface) { + xr_server->set_primary_interface(Ref<XRInterface>()); + } + xr_server->remove_interface(openxr_interface); } // and release @@ -87,5 +125,6 @@ void unregister_openxr_types() { if (openxr_api) { openxr_api->finish(); memdelete(openxr_api); + openxr_api = nullptr; } } diff --git a/modules/openxr/register_types.h b/modules/openxr/register_types.h index fb42770750..1b3d98422d 100644 --- a/modules/openxr/register_types.h +++ b/modules/openxr/register_types.h @@ -33,8 +33,9 @@ #define MODULE_OPENXR_HAS_PREREGISTER -void preregister_openxr_types(); -void register_openxr_types(); -void unregister_openxr_types(); +#include "modules/register_module_types.h" + +void initialize_openxr_module(ModuleInitializationLevel p_level); +void uninitialize_openxr_module(ModuleInitializationLevel p_level); #endif // OPENXR_REGISTER_TYPES_H |