From b65b367a6f2856b0c459b05496db7bac13fd11cb Mon Sep 17 00:00:00 2001 From: Bastiaan Olij Date: Mon, 12 Aug 2024 20:25:43 +1000 Subject: [PATCH] OpenXR: Adding support for the render model extension --- doc/classes/ProjectSettings.xml | 4 + main/main.cpp | 1 + modules/openxr/config.py | 3 + .../doc_classes/OpenXRExtensionWrapper.xml | 6 + .../openxr/doc_classes/OpenXRRenderModel.xml | 32 + .../OpenXRRenderModelExtension.xml | 129 +++ .../doc_classes/OpenXRRenderModelManager.xml | 48 ++ .../extensions/openxr_extension_wrapper.cpp | 5 + .../extensions/openxr_extension_wrapper.h | 2 + .../openxr_render_model_extension.cpp | 794 ++++++++++++++++++ .../openxr_render_model_extension.h | 168 ++++ modules/openxr/openxr_api.cpp | 20 +- modules/openxr/openxr_api.h | 1 + modules/openxr/openxr_uuid.h | 52 ++ modules/openxr/register_types.cpp | 11 + modules/openxr/scene/openxr_render_model.cpp | 168 ++++ modules/openxr/scene/openxr_render_model.h | 60 ++ .../scene/openxr_render_model_manager.cpp | 284 +++++++ .../scene/openxr_render_model_manager.h | 84 ++ 19 files changed, 1871 insertions(+), 1 deletion(-) create mode 100644 modules/openxr/doc_classes/OpenXRRenderModel.xml create mode 100644 modules/openxr/doc_classes/OpenXRRenderModelExtension.xml create mode 100644 modules/openxr/doc_classes/OpenXRRenderModelManager.xml create mode 100644 modules/openxr/extensions/openxr_render_model_extension.cpp create mode 100644 modules/openxr/extensions/openxr_render_model_extension.h create mode 100644 modules/openxr/openxr_uuid.h create mode 100644 modules/openxr/scene/openxr_render_model.cpp create mode 100644 modules/openxr/scene/openxr_render_model.h create mode 100644 modules/openxr/scene/openxr_render_model_manager.cpp create mode 100644 modules/openxr/scene/openxr_render_model_manager.h diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 5cb76b0b87e..277d0013c95 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -3451,6 +3451,10 @@ If [code]true[/code], support for the unobstructed data source is requested. If supported, you will receive hand tracking data based on the actual finger positions of the user often determined by optical tracking. [b]Note:[/b] This requires the OpenXR data source extension and unobstructed handtracking to be supported by the XR runtime. If not supported this setting will be ignored. [member xr/openxr/extensions/hand_tracking] must be enabled for this setting to be used. + + If [code]true[/code] we enable the render model extension if available. + [b]Note:[/b] This relates to the core OpenXR render model extension and has no relation to any vendor render model extensions. + Specify whether OpenXR should be configured for an HMD or a hand held device. diff --git a/main/main.cpp b/main/main.cpp index ec7485bdf29..638a1d12663 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2771,6 +2771,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking_controller_data_source", false); // XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT GLOBAL_DEF_RST_BASIC("xr/openxr/extensions/hand_interaction_profile", false); GLOBAL_DEF_RST_BASIC("xr/openxr/extensions/eye_gaze_interaction", false); + GLOBAL_DEF_BASIC("xr/openxr/extensions/render_model", false); // OpenXR Binding modifier settings GLOBAL_DEF_BASIC("xr/openxr/binding_modifiers/analog_threshold", false); diff --git a/modules/openxr/config.py b/modules/openxr/config.py index a1b44685d7c..92187860be5 100644 --- a/modules/openxr/config.py +++ b/modules/openxr/config.py @@ -40,6 +40,9 @@ def get_doc_classes(): "OpenXRBindingModifierEditor", "OpenXRHapticBase", "OpenXRHapticVibration", + "OpenXRRenderModelExtension", + "OpenXRRenderModel", + "OpenXRRenderModelManager", ] diff --git a/modules/openxr/doc_classes/OpenXRExtensionWrapper.xml b/modules/openxr/doc_classes/OpenXRExtensionWrapper.xml index b3106c1003e..38df82b9d30 100644 --- a/modules/openxr/doc_classes/OpenXRExtensionWrapper.xml +++ b/modules/openxr/doc_classes/OpenXRExtensionWrapper.xml @@ -185,6 +185,12 @@ Called when the OpenXR session state is changed to visible. This means OpenXR is now ready to receive frames. + + + + Called when OpenXR has performed its action sync. + + diff --git a/modules/openxr/doc_classes/OpenXRRenderModel.xml b/modules/openxr/doc_classes/OpenXRRenderModel.xml new file mode 100644 index 00000000000..e9f531e28d3 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRRenderModel.xml @@ -0,0 +1,32 @@ + + + + This node will display an OpenXR render model. + + + This node will display an OpenXR render model by accessing the associated GLTF and processes all animation data (if supported by the XR runtime). + Render models were introduced to allow showing the correct model for the controller (or other device) the user has in hand, since the OpenXR action map does not provide information about the hardware used by the user. Note that while the controller (or device) can be somewhat inferred by the bound action map profile, this is a dangerous approach as the user may be using hardware not known at time of development and OpenXR will simply simulate an available interaction profile. + + + + + + + + Returns the top level path related to this render model. + + + + + + The render model RID for the render model to load, as returned by [method OpenXRRenderModelExtension.render_model_create] or [method OpenXRRenderModelExtension.render_model_get_all]. + + + + + + Emitted when the top level path of this render model has changed. + + + + diff --git a/modules/openxr/doc_classes/OpenXRRenderModelExtension.xml b/modules/openxr/doc_classes/OpenXRRenderModelExtension.xml new file mode 100644 index 00000000000..1f048dcc145 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRRenderModelExtension.xml @@ -0,0 +1,129 @@ + + + + This class implements the OpenXR Render Model Extension. + + + This class implements the OpenXR Render Model Extension, if enabled it will maintain a list of active render models and provides an interface to the render model data. + + + + + + + + Returns [code]true[/code] if OpenXR's render model extension is supported and enabled. + [b]Note:[/b] This only returns a valid value after OpenXR has been initialized. + + + + + + + Creates a render model object within OpenXR using a render model id. + [b]Note:[/b] This function is exposed for dependent OpenXR extensions that provide render model ids to be used with the render model extension. + + + + + + + Destroys a render model object within OpenXR that was previously created with [method render_model_create]. + [b]Note:[/b] This function is exposed for dependent OpenXR extensions that provide render model ids to be used with the render model extension. + + + + + + Returns an array of all currently active render models registered with this extension. + + + + + + + Returns the number of animatable nodes this render model has. + + + + + + + + Returns the name of the given animatable node. + + + + + + + + Returns the current local transform for an animatable node. This is updated every frame. + + + + + + + Returns the tracking confidence of the tracking data for the render model. + + + + + + + Returns the root transform of a render model. This is the tracked position relative to our [XROrigin3D] node. + + + + + + + Returns a list of active subaction paths for this [param render_model]. + [b]Note:[/b] If different devices are bound to your actions than available in suggested interaction bindings, this information shows paths related to the interaction bindings being mimicked by that device. + + + + + + + Returns the top level path associated with this [param render_model]. If provided this identifies whether the render model is associated with the players hands or other body part. + + + + + + + + Returns [code]true[/code] if this animatable node should be visible. + + + + + + + Returns an instance of a subscene that contains all [MeshInstance3D] nodes that allow you to visualize the render model. + + + + + + + + Emitted when a new render model is added. + + + + + + Emitted when a render model is removed. + + + + + + Emitted when the top level path associated with a render model changed. + + + + diff --git a/modules/openxr/doc_classes/OpenXRRenderModelManager.xml b/modules/openxr/doc_classes/OpenXRRenderModelManager.xml new file mode 100644 index 00000000000..4814dba90e2 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRRenderModelManager.xml @@ -0,0 +1,48 @@ + + + + Helper node that will automatically manage displaying render models. + + + This helper node will automatically manage displaying render models. It will create new [OpenXRRenderModel] nodes as controllers and other hand held devices are detected, and remove those nodes when they are deactivated. + [b]Note:[/b] If you want more control over this logic you can alternatively call [method OpenXRRenderModelExtension.render_model_get_all] to obtain a list of active render model ids and create [OpenXRRenderModel] instances for each render model id provided. + + + + + + Position render models local to this pose (this will adjust the position of the render models container node). + + + Limits render models to the specified tracker. Include: 0 = All render models, 1 = Render models not related to a tracker, 2 = Render models related to the left hand tracker, 3 = Render models related to the right hand tracker. + + + + + + + Emitted when a render model node is added as a child to this node. + + + + + + Emitted when a render model child node is about to be removed from this node. + + + + + + All active render models are shown regardless of what tracker they relate to. + + + Only active render models are shown that are not related to any tracker we manage. + + + Only active render models are shown that are related to the left hand tracker. + + + Only active render models are shown that are related to the right hand tracker. + + + diff --git a/modules/openxr/extensions/openxr_extension_wrapper.cpp b/modules/openxr/extensions/openxr_extension_wrapper.cpp index 866d4c09ad2..f2345acc97f 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper.cpp +++ b/modules/openxr/extensions/openxr_extension_wrapper.cpp @@ -55,6 +55,7 @@ void OpenXRExtensionWrapper::_bind_methods() { GDVIRTUAL_BIND(_on_instance_destroyed); GDVIRTUAL_BIND(_on_session_created, "session"); GDVIRTUAL_BIND(_on_process); + GDVIRTUAL_BIND(_on_sync_actions); GDVIRTUAL_BIND(_on_pre_render); GDVIRTUAL_BIND(_on_main_swapchains_created); GDVIRTUAL_BIND(_on_pre_draw_viewport, "viewport"); @@ -252,6 +253,10 @@ void OpenXRExtensionWrapper::on_process() { GDVIRTUAL_CALL(_on_process); } +void OpenXRExtensionWrapper::on_sync_actions() { + GDVIRTUAL_CALL(_on_sync_actions); +} + void OpenXRExtensionWrapper::on_pre_render() { GDVIRTUAL_CALL(_on_pre_render); } diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h index 72b7a607aed..7fdda0eb7e7 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper.h +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -120,6 +120,7 @@ public: // this happens right before physics process and normal processing is run. // This is when controller data is queried and made available to game logic. virtual void on_process(); + virtual void on_sync_actions(); // `on_sync_actions` is called right after we sync our action sets. virtual void on_pre_render(); // `on_pre_render` is called right before we start rendering our XR viewports. virtual void on_main_swapchains_created(); // `on_main_swapchains_created` is called right after our main swapchains are (re)created. virtual void on_pre_draw_viewport(RID p_render_target); // `on_pre_draw_viewport` is called right before we start rendering this viewport @@ -131,6 +132,7 @@ public: GDVIRTUAL0(_on_instance_destroyed); GDVIRTUAL1(_on_session_created, uint64_t); GDVIRTUAL0(_on_process); + GDVIRTUAL0(_on_sync_actions); GDVIRTUAL0(_on_pre_render); GDVIRTUAL0(_on_main_swapchains_created); GDVIRTUAL0(_on_session_destroyed); diff --git a/modules/openxr/extensions/openxr_render_model_extension.cpp b/modules/openxr/extensions/openxr_render_model_extension.cpp new file mode 100644 index 00000000000..a9b6910fc88 --- /dev/null +++ b/modules/openxr/extensions/openxr_render_model_extension.cpp @@ -0,0 +1,794 @@ +/**************************************************************************/ +/* openxr_render_model_extension.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "openxr_render_model_extension.h" + +#include "../openxr_api.h" +#include "../openxr_interface.h" + +#include "core/config/project_settings.h" +#include "core/string/print_string.h" +#include "servers/xr_server.h" + +OpenXRRenderModelExtension *OpenXRRenderModelExtension::singleton = nullptr; + +OpenXRRenderModelExtension *OpenXRRenderModelExtension::get_singleton() { + return singleton; +} + +void OpenXRRenderModelExtension::_bind_methods() { + ClassDB::bind_method(D_METHOD("is_active"), &OpenXRRenderModelExtension::is_active); + ClassDB::bind_method(D_METHOD("render_model_create", "render_model_id"), &OpenXRRenderModelExtension::render_model_create); + ClassDB::bind_method(D_METHOD("render_model_destroy", "render_model"), &OpenXRRenderModelExtension::render_model_destroy); + ClassDB::bind_method(D_METHOD("render_model_get_all"), &OpenXRRenderModelExtension::render_model_get_all); + ClassDB::bind_method(D_METHOD("render_model_new_scene_instance", "render_model"), &OpenXRRenderModelExtension::render_model_new_scene_instance); + ClassDB::bind_method(D_METHOD("render_model_get_subaction_paths", "render_model"), &OpenXRRenderModelExtension::render_model_get_subaction_paths); + ClassDB::bind_method(D_METHOD("render_model_get_top_level_path", "render_model"), &OpenXRRenderModelExtension::render_model_get_top_level_path_as_string); + ClassDB::bind_method(D_METHOD("render_model_get_confidence", "render_model"), &OpenXRRenderModelExtension::render_model_get_confidence); + ClassDB::bind_method(D_METHOD("render_model_get_root_transform", "render_model"), &OpenXRRenderModelExtension::render_model_get_root_transform); + ClassDB::bind_method(D_METHOD("render_model_get_animatable_node_count", "render_model"), &OpenXRRenderModelExtension::render_model_get_animatable_node_count); + ClassDB::bind_method(D_METHOD("render_model_get_animatable_node_name", "render_model", "index"), &OpenXRRenderModelExtension::render_model_get_animatable_node_name); + ClassDB::bind_method(D_METHOD("render_model_is_animatable_node_visible", "render_model", "index"), &OpenXRRenderModelExtension::render_model_is_animatable_node_visible); + ClassDB::bind_method(D_METHOD("render_model_get_animatable_node_transform", "render_model", "index"), &OpenXRRenderModelExtension::render_model_get_animatable_node_transform); + + ADD_SIGNAL(MethodInfo("render_model_added", PropertyInfo(Variant::RID, "render_model"))); + ADD_SIGNAL(MethodInfo("render_model_removed", PropertyInfo(Variant::RID, "render_model"))); + ADD_SIGNAL(MethodInfo("render_model_top_level_path_changed", PropertyInfo(Variant::RID, "render_model"))); +} + +OpenXRRenderModelExtension::OpenXRRenderModelExtension() { + singleton = this; +} + +OpenXRRenderModelExtension::~OpenXRRenderModelExtension() { + singleton = nullptr; +} + +HashMap OpenXRRenderModelExtension::get_requested_extensions() { + HashMap request_extensions; + + if (GLOBAL_GET("xr/openxr/extensions/render_model")) { + request_extensions[XR_EXT_UUID_EXTENSION_NAME] = &uuid_ext; + request_extensions[XR_EXT_RENDER_MODEL_EXTENSION_NAME] = &render_model_ext; + request_extensions[XR_EXT_INTERACTION_RENDER_MODEL_EXTENSION_NAME] = &interaction_render_model_ext; + } + + return request_extensions; +} + +void OpenXRRenderModelExtension::on_instance_created(const XrInstance p_instance) { + // Standard entry points we use. + EXT_INIT_XR_FUNC(xrLocateSpace); + EXT_INIT_XR_FUNC(xrDestroySpace); + EXT_INIT_XR_FUNC(xrPathToString); + + if (render_model_ext) { + EXT_INIT_XR_FUNC(xrCreateRenderModelEXT); + EXT_INIT_XR_FUNC(xrDestroyRenderModelEXT); + EXT_INIT_XR_FUNC(xrGetRenderModelPropertiesEXT); + EXT_INIT_XR_FUNC(xrCreateRenderModelSpaceEXT); + EXT_INIT_XR_FUNC(xrCreateRenderModelAssetEXT); + EXT_INIT_XR_FUNC(xrDestroyRenderModelAssetEXT); + EXT_INIT_XR_FUNC(xrGetRenderModelAssetDataEXT); + EXT_INIT_XR_FUNC(xrGetRenderModelAssetPropertiesEXT); + EXT_INIT_XR_FUNC(xrGetRenderModelStateEXT); + } + + if (interaction_render_model_ext) { + EXT_INIT_XR_FUNC(xrEnumerateInteractionRenderModelIdsEXT); + EXT_INIT_XR_FUNC(xrEnumerateRenderModelSubactionPathsEXT); + EXT_INIT_XR_FUNC(xrGetRenderModelPoseTopLevelUserPathEXT); + } +} + +void OpenXRRenderModelExtension::on_session_created(const XrSession p_session) { + _interaction_data_dirty = true; +} + +void OpenXRRenderModelExtension::on_instance_destroyed() { + xrCreateRenderModelEXT_ptr = nullptr; + xrDestroyRenderModelEXT_ptr = nullptr; + xrGetRenderModelPropertiesEXT_ptr = nullptr; + xrCreateRenderModelSpaceEXT_ptr = nullptr; + xrCreateRenderModelAssetEXT_ptr = nullptr; + xrDestroyRenderModelAssetEXT_ptr = nullptr; + xrGetRenderModelAssetDataEXT_ptr = nullptr; + xrGetRenderModelAssetPropertiesEXT_ptr = nullptr; + xrGetRenderModelStateEXT_ptr = nullptr; + xrEnumerateInteractionRenderModelIdsEXT_ptr = nullptr; + xrEnumerateRenderModelSubactionPathsEXT_ptr = nullptr; + xrGetRenderModelPoseTopLevelUserPathEXT_ptr = nullptr; + + uuid_ext = false; + render_model_ext = false; + interaction_render_model_ext = false; +} + +void OpenXRRenderModelExtension::on_session_destroyed() { + _clear_interaction_data(); + _clear_render_model_data(); + + // We no longer have valid sync data. + xr_sync_has_run = false; +} + +bool OpenXRRenderModelExtension::on_event_polled(const XrEventDataBuffer &event) { + if (event.type == XR_TYPE_EVENT_DATA_INTERACTION_RENDER_MODELS_CHANGED_EXT) { + // Mark interaction data as dirty so that we update it on sync. + _interaction_data_dirty = true; + + return true; + } else if (event.type == XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED) { + // If our controller bindings changed, its likely our render models change too. + // We should be getting a XR_TYPE_EVENT_DATA_INTERACTION_RENDER_MODELS_CHANGED_EXT + // but checking for this scenario just in case. + _interaction_data_dirty = true; + + // Do not consider this handled, we simply do additional logic. + return false; + } + + return false; +} + +void OpenXRRenderModelExtension::on_sync_actions() { + if (!is_active()) { + return; + } + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + + // Mark sync as run + xr_sync_has_run = true; + + // Update our interaction data if needed + if (_interaction_data_dirty) { + _update_interaction_data(); + } + + // Loop through all of our render models to update our space and state info + LocalVector owned = render_model_owner.get_owned_list(); + + for (const RID &rid : owned) { + RenderModel *render_model = render_model_owner.get_or_null(rid); + if (render_model && render_model->xr_space != XR_NULL_HANDLE) { + XrSpaceLocation render_model_location = { + XR_TYPE_SPACE_LOCATION, // type + nullptr, // next + 0, // locationFlags + { { 0.0, 0.0, 0.0, 1.0 }, { 0.0, 0.0, 0.0 } }, // pose + }; + + XrResult result = xrLocateSpace(render_model->xr_space, openxr_api->get_play_space(), openxr_api->get_predicted_display_time(), &render_model_location); + ERR_CONTINUE_MSG(XR_FAILED(result), "OpenXR: Failed to locate render model space [" + openxr_api->get_error_string(result) + "]"); + + render_model->confidence = openxr_api->transform_from_location(render_model_location, render_model->root_transform); + + if (!render_model->node_states.is_empty()) { + // Get node states. + XrRenderModelStateGetInfoEXT get_state_info = { + XR_TYPE_RENDER_MODEL_STATE_GET_INFO_EXT, // type + nullptr, // next + openxr_api->get_predicted_display_time() // displayTime + }; + + XrRenderModelStateEXT state = { + XR_TYPE_RENDER_MODEL_STATE_EXT, // type + nullptr, // next + render_model->animatable_node_count, // nodeStateCount + render_model->node_states.ptr(), // nodeStates + }; + + result = xrGetRenderModelStateEXT(render_model->xr_render_model, &get_state_info, &state); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to update node states [" + openxr_api->get_error_string(result) + "]"); + } + } + + XrPath new_path = XR_NULL_PATH; + + if (toplevel_paths.is_empty()) { + // Set this up just once with paths we support here. + toplevel_paths.push_back(openxr_api->get_xr_path("/user/hand/left")); + toplevel_paths.push_back(openxr_api->get_xr_path("/user/hand/right")); + } + + XrInteractionRenderModelTopLevelUserPathGetInfoEXT info = { + XR_TYPE_INTERACTION_RENDER_MODEL_TOP_LEVEL_USER_PATH_GET_INFO_EXT, // type + nullptr, // next + (uint32_t)toplevel_paths.size(), // topLevelUserPathCount + toplevel_paths.ptr() // topLevelUserPaths + }; + result = xrGetRenderModelPoseTopLevelUserPathEXT(render_model->xr_render_model, &info, &new_path); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to update the top level path for render models [" + openxr_api->get_error_string(result) + "]"); + } else if (new_path != render_model->top_level_path) { + print_verbose("OpenXR: Render model top level path changed to " + openxr_api->get_xr_path_name(new_path)); + + // Set the new path + render_model->top_level_path = new_path; + + // And broadcast it + // Note, converting an XrPath to a String has overhead, so we won't do this automatically. + emit_signal(SNAME("render_model_top_level_path_changed"), rid); + } + } + } +} + +bool OpenXRRenderModelExtension::is_active() const { + return render_model_ext && interaction_render_model_ext; +} + +void OpenXRRenderModelExtension::_clear_interaction_data() { + for (const KeyValue &e : interaction_render_models) { + render_model_destroy(e.value); + } + interaction_render_models.clear(); +} + +bool OpenXRRenderModelExtension::_update_interaction_data() { + ERR_FAIL_COND_V_MSG(!interaction_render_model_ext, false, "Interaction render model extension hasn't been enabled."); + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, false); + + XrSession session = openxr_api->get_session(); + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + // Check if syncActions has been run at least once or there is no point in getting data. + if (!xr_sync_has_run) { + // Do not treat this as an error. + return true; + } + + // If we get this far, no longer mark as dirty. + // Else we just repeat the same error over and over again. + _interaction_data_dirty = false; + + // Obtain interaction info. + XrInteractionRenderModelIdsEnumerateInfoEXT interaction_info = { + XR_TYPE_INTERACTION_RENDER_MODEL_IDS_ENUMERATE_INFO_EXT, // type + nullptr, // next + }; + + // Obtain count. + uint32_t interaction_count = 0; + XrResult result = xrEnumerateInteractionRenderModelIdsEXT(session, &interaction_info, 0, &interaction_count, nullptr); + if (XR_FAILED(result)) { + // not successful? then we do nothing. + ERR_FAIL_V_MSG(false, "OpenXR: Failed to obtain render model interaction id count [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + // Create some storage + LocalVector render_model_interaction_ids; + render_model_interaction_ids.resize(interaction_count); + + // Only need to fetch data if there is something to fetch (/we've got storage). + if (!render_model_interaction_ids.is_empty()) { + // Obtain interaction ids + result = xrEnumerateInteractionRenderModelIdsEXT(session, &interaction_info, render_model_interaction_ids.size(), &interaction_count, render_model_interaction_ids.ptr()); + if (XR_FAILED(result)) { + ERR_FAIL_V_MSG(false, "OpenXR: Failed to obtain render model interaction ids [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + } + + // Remove render models that are no longer tracked + LocalVector erase_ids; + for (const KeyValue &e : interaction_render_models) { + if (!render_model_interaction_ids.has(e.key)) { + if (e.value.is_valid()) { + render_model_destroy(e.value); + } + + erase_ids.push_back(e.key); + } + } + + // Remove these from our hashmap + for (const XrRenderModelIdEXT &id : erase_ids) { + interaction_render_models.erase(id); + } + + // Now update our models + for (const XrRenderModelIdEXT &id : render_model_interaction_ids) { + if (!interaction_render_models.has(id)) { + // Even if this fails we add it so we don't repeat trying to create it + interaction_render_models[id] = render_model_create(id); + } + } + + return true; +} + +bool OpenXRRenderModelExtension::has_render_model(RID p_render_model) const { + return render_model_owner.owns(p_render_model); +} + +RID OpenXRRenderModelExtension::render_model_create(XrRenderModelIdEXT p_render_model_id) { + ERR_FAIL_COND_V_MSG(!render_model_ext, RID(), "Render model extension hasn't been enabled."); + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, RID()); + + XrSession session = openxr_api->get_session(); + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, RID()); + + RenderModel render_model; + render_model.xr_render_model_id = p_render_model_id; + + // Start with the extensions that are supported in our base (see GLTFDocument::_parse_gltf_extensions). + Vector supported_gltf_extensions = { + "KHR_lights_punctual", + "KHR_materials_pbrSpecularGlossiness", + "KHR_texture_transform", + "KHR_materials_unlit", + "KHR_materials_emissive_strength", + }; + + // Now find anything we support through plugins, which is a bit of a pain as they are converted to Strings + // and we need to convert them back. + Vector char_extensions; // Just for temp storage of our c-strings. + Vector> gltf_document_extensions = GLTFDocument::get_all_gltf_document_extensions(); + for (Ref &gltf_document_extension : gltf_document_extensions) { + Vector supported_extensions = gltf_document_extension->get_supported_extensions(); + for (const String &extension : supported_extensions) { + char_extensions.push_back(extension.utf8()); + } + } + + // Now we can add them to our supported extensions list. + for (const CharString &cs : char_extensions) { + supported_gltf_extensions.push_back(cs.get_data()); + } + + XrRenderModelCreateInfoEXT create_info = { + XR_TYPE_RENDER_MODEL_CREATE_INFO_EXT, // type + nullptr, // next + p_render_model_id, // renderModelId + uint32_t(supported_gltf_extensions.size()), // gltfExtensionCount + supported_gltf_extensions.ptr(), // gltfExtensions + }; + + XrResult result = xrCreateRenderModelEXT(session, &create_info, &render_model.xr_render_model); + if (XR_FAILED(result)) { + ERR_FAIL_V_MSG(RID(), "OpenXR: Failed to create render model [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + XrRenderModelPropertiesGetInfoEXT properties_info = { + XR_TYPE_RENDER_MODEL_PROPERTIES_GET_INFO_EXT, // type + nullptr, // next + }; + + XrRenderModelPropertiesEXT properties; + result = xrGetRenderModelPropertiesEXT(render_model.xr_render_model, &properties_info, &properties); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to get render model properties [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } else { + render_model.animatable_node_count = properties.animatableNodeCount; + render_model.render_model_data = _get_render_model_data(properties.cacheId, properties.animatableNodeCount); + } + + // Create space for positioning our asset. + XrRenderModelSpaceCreateInfoEXT space_create_info = { + XR_TYPE_RENDER_MODEL_SPACE_CREATE_INFO_EXT, // type + nullptr, // next + render_model.xr_render_model // renderModel + }; + + result = xrCreateRenderModelSpaceEXT(session, &space_create_info, &render_model.xr_space); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to create render model space [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + if (render_model.animatable_node_count > 0) { + render_model.node_states.resize(render_model.animatable_node_count); + } + + RID new_rid = render_model_owner.make_rid(render_model); + + emit_signal(SNAME("render_model_added"), new_rid); + + return new_rid; +} + +RID OpenXRRenderModelExtension::_render_model_create(uint64_t p_render_model_id) { + RID ret; + + ERR_FAIL_COND_V(p_render_model_id == XR_NULL_RENDER_MODEL_ID_EXT, ret); + + if (is_active()) { + ret = render_model_create(XrRenderModelIdEXT(p_render_model_id)); + } + + return ret; +} + +void OpenXRRenderModelExtension::render_model_destroy(RID p_render_model) { + ERR_FAIL_COND_MSG(!render_model_ext, "Render model extension hasn't been enabled."); + + RenderModel *render_model = render_model_owner.get_or_null(p_render_model); + ERR_FAIL_NULL(render_model); + + emit_signal(SNAME("render_model_removed"), p_render_model); + + // Clean up. + if (render_model->xr_space != XR_NULL_HANDLE) { + xrDestroySpace(render_model->xr_space); + } + + render_model->node_states.clear(); + + // And destroy our model. + XrResult result = xrDestroyRenderModelEXT(render_model->xr_render_model); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to destroy render model [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + render_model_owner.free(p_render_model); +} + +TypedArray OpenXRRenderModelExtension::render_model_get_all() { + TypedArray ret; + + LocalVector rids = render_model_owner.get_owned_list(); + + for (const RID &rid : rids) { + ret.push_back(rid); + } + + return ret; +} + +Node3D *OpenXRRenderModelExtension::render_model_new_scene_instance(RID p_render_model) const { + RenderModel *render_model = render_model_owner.get_or_null(p_render_model); + ERR_FAIL_NULL_V(render_model, nullptr); + + if (render_model->render_model_data.is_null()) { + // We never loaded it (don't spam errors here). + return nullptr; + } + + return render_model->render_model_data->new_scene_instance(); +} + +PackedStringArray OpenXRRenderModelExtension::render_model_get_subaction_paths(RID p_render_model) { + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, PackedStringArray()); + + XrInstance instance = openxr_api->get_instance(); + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, PackedStringArray()); + + RenderModel *render_model = render_model_owner.get_or_null(p_render_model); + ERR_FAIL_NULL_V(render_model, PackedStringArray()); + + PackedStringArray subaction_paths; + + XrInteractionRenderModelSubactionPathInfoEXT subaction_info = { + XR_TYPE_INTERACTION_RENDER_MODEL_SUBACTION_PATH_INFO_EXT, // type + nullptr, // next + }; + + uint32_t capacity; + + XrResult result = xrEnumerateRenderModelSubactionPathsEXT(render_model->xr_render_model, &subaction_info, 0, &capacity, nullptr); + if (XR_FAILED(result)) { + ERR_FAIL_V_MSG(PackedStringArray(), "OpenXR: Failed to obtain render model subaction path count [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + if (capacity > 0) { + LocalVector paths; + + paths.resize(capacity); + + result = xrEnumerateRenderModelSubactionPathsEXT(render_model->xr_render_model, &subaction_info, capacity, &capacity, paths.ptr()); + if (XR_FAILED(result)) { + ERR_FAIL_V_MSG(PackedStringArray(), "OpenXR: Failed to obtain render model subaction paths [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + for (uint32_t i = 0; i < capacity; i++) { + char buffer[1024]; + uint32_t size = 0; + xrPathToString(instance, paths[i], 1024, &size, buffer); + if (size > 0) { + subaction_paths.push_back(String(buffer)); + } + } + } + + return subaction_paths; +} + +XrPath OpenXRRenderModelExtension::render_model_get_top_level_path(RID p_render_model) const { + RenderModel *render_model = render_model_owner.get_or_null(p_render_model); + ERR_FAIL_NULL_V(render_model, XRPose::TrackingConfidence::XR_TRACKING_CONFIDENCE_NONE); + + return render_model->top_level_path; +} + +String OpenXRRenderModelExtension::render_model_get_top_level_path_as_string(RID p_render_model) const { + String ret; + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, ret); + + if (is_active() && has_render_model(p_render_model)) { + XrPath path = render_model_get_top_level_path(p_render_model); + if (path == XR_NULL_PATH) { + return "None"; + } else { + return openxr_api->get_xr_path_name(path); + } + } + + return ret; +} + +XRPose::TrackingConfidence OpenXRRenderModelExtension::render_model_get_confidence(RID p_render_model) const { + RenderModel *render_model = render_model_owner.get_or_null(p_render_model); + ERR_FAIL_NULL_V(render_model, XRPose::TrackingConfidence::XR_TRACKING_CONFIDENCE_NONE); + + return render_model->confidence; +} + +Transform3D OpenXRRenderModelExtension::render_model_get_root_transform(RID p_render_model) const { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL_V(xr_server, Transform3D()); + + RenderModel *render_model = render_model_owner.get_or_null(p_render_model); + ERR_FAIL_NULL_V(render_model, Transform3D()); + + // Scale our root transform + real_t world_scale = xr_server->get_world_scale(); + Transform3D root_transform = render_model->root_transform.scaled(Vector3(world_scale, world_scale, world_scale)); + + return xr_server->get_reference_frame() * root_transform; +} + +uint32_t OpenXRRenderModelExtension::render_model_get_animatable_node_count(RID p_render_model) const { + RenderModel *render_model = render_model_owner.get_or_null(p_render_model); + ERR_FAIL_NULL_V(render_model, 0); + + return render_model->animatable_node_count; +} + +String OpenXRRenderModelExtension::render_model_get_animatable_node_name(RID p_render_model, uint32_t p_index) const { + RenderModel *render_model = render_model_owner.get_or_null(p_render_model); + ERR_FAIL_NULL_V(render_model, String()); + + if (render_model->render_model_data.is_null()) { + // We never loaded it (don't spam errors here). + return String(); + } + + return render_model->render_model_data->get_node_name(p_index); +} + +bool OpenXRRenderModelExtension::render_model_is_animatable_node_visible(RID p_render_model, uint32_t p_index) const { + RenderModel *render_model = render_model_owner.get_or_null(p_render_model); + ERR_FAIL_NULL_V(render_model, false); + + ERR_FAIL_UNSIGNED_INDEX_V(p_index, render_model->animatable_node_count, false); + + if (render_model->node_states.is_empty()) { + // Never allocated (don't spam errors here). + return false; + } + + return render_model->node_states[p_index].isVisible; +} + +Transform3D OpenXRRenderModelExtension::render_model_get_animatable_node_transform(RID p_render_model, uint32_t p_index) const { + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, Transform3D()); + + RenderModel *render_model = render_model_owner.get_or_null(p_render_model); + ERR_FAIL_NULL_V(render_model, Transform3D()); + + ERR_FAIL_UNSIGNED_INDEX_V(p_index, render_model->animatable_node_count, Transform3D()); + + if (render_model->node_states.is_empty()) { + // Never allocated (don't spam errors here). + return Transform3D(); + } + + return openxr_api->transform_from_pose(render_model->node_states[p_index].nodePose); +} + +Ref OpenXRRenderModelExtension::_get_render_model_data(XrUuidEXT p_cache_id, uint32_t p_animatable_node_count) { + if (render_model_data_cache.has(p_cache_id)) { + return render_model_data_cache[p_cache_id]; + } + + // We don't have this cached, lets load it up + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, nullptr); + + XrSession session = openxr_api->get_session(); + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, nullptr); + + XrRenderModelAssetEXT asset; + + XrRenderModelAssetCreateInfoEXT create_info = { + XR_TYPE_RENDER_MODEL_ASSET_CREATE_INFO_EXT, // type + nullptr, // next + p_cache_id // cacheId + }; + + XrResult result = xrCreateRenderModelAssetEXT(session, &create_info, &asset); + if (XR_FAILED(result)) { + ERR_FAIL_V_MSG(nullptr, "OpenXR: Failed to create render model asset [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + Ref render_model_data = _load_asset(asset, p_animatable_node_count); + + // We're done with this :) + result = xrDestroyRenderModelAssetEXT(asset); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to destroy render model asset [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + // And cache it + render_model_data_cache[p_cache_id] = render_model_data; + + return render_model_data; +} + +Ref OpenXRRenderModelExtension::_load_asset(XrRenderModelAssetEXT p_asset, uint32_t p_animatable_node_count) { + XrRenderModelAssetDataGetInfoEXT get_info = { + XR_TYPE_RENDER_MODEL_ASSET_DATA_GET_INFO_EXT, // type + nullptr, // next + }; + + XrRenderModelAssetDataEXT asset_data = { + XR_TYPE_RENDER_MODEL_ASSET_DATA_EXT, // type + nullptr, // next + 0, // bufferCapacityInput; + 0, // bufferCountOutput; + nullptr // buffer; + }; + + // Obtain required size for the buffer. + XrResult result = xrGetRenderModelAssetDataEXT(p_asset, &get_info, &asset_data); + if (XR_FAILED(result)) { + ERR_FAIL_V_MSG(nullptr, "OpenXR: Failed to get render model buffer size [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + ERR_FAIL_COND_V(asset_data.bufferCountOutput == 0, nullptr); + + // Allocate data + PackedByteArray buffer; + buffer.resize(asset_data.bufferCountOutput); + asset_data.buffer = buffer.ptrw(); + asset_data.bufferCapacityInput = asset_data.bufferCountOutput; + + // Now get our actual data. + result = xrGetRenderModelAssetDataEXT(p_asset, &get_info, &asset_data); + if (XR_FAILED(result)) { + ERR_FAIL_V_MSG(nullptr, "OpenXR: Failed to get render model buffer [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + // Get the names of any animatable nodes + PackedStringArray node_names; + if (p_animatable_node_count > 0) { + Vector node_properties; + node_properties.resize(p_animatable_node_count); + + XrRenderModelAssetPropertiesGetInfoEXT properties_info = { + XR_TYPE_RENDER_MODEL_ASSET_PROPERTIES_GET_INFO_EXT, // type + nullptr, // next + }; + + XrRenderModelAssetPropertiesEXT asset_properties = { + XR_TYPE_RENDER_MODEL_ASSET_PROPERTIES_EXT, // type + nullptr, // next + uint32_t(node_properties.size()), // nodePropertyCount + node_properties.ptrw(), // nodeProperties + }; + + result = xrGetRenderModelAssetPropertiesEXT(p_asset, &properties_info, &asset_properties); + if (XR_FAILED(result)) { + ERR_FAIL_V_MSG(nullptr, "OpenXR: Failed to get render model property info [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + node_names.resize(p_animatable_node_count); + String *node_names_ptrw = node_names.ptrw(); + for (uint32_t i = 0; i < p_animatable_node_count; i++) { + node_names_ptrw[i] = String(node_properties[i].uniqueName); + } + } + + Ref render_model_data; + render_model_data.instantiate(); + + render_model_data->parse_gltf_document(buffer); + render_model_data->set_node_names(node_names); + + return render_model_data; +} + +void OpenXRRenderModelExtension::_clear_render_model_data() { + // Clear our toplevel paths filter. + toplevel_paths.clear(); + + // Clear our render model cache. + render_model_data_cache.clear(); + + // Loop through all of our render models and destroy them. + LocalVector owned = render_model_owner.get_owned_list(); + for (const RID &rid : owned) { + render_model_destroy(rid); + } +} + +bool OpenXRRenderModelData::parse_gltf_document(const PackedByteArray &p_bytes) { + // State holds our data, document parses GLTF + Ref new_state; + new_state.instantiate(); + Ref new_gltf_document; + new_gltf_document.instantiate(); + + Error err = new_gltf_document->append_from_buffer(p_bytes, "", new_state); + if (err != OK) { + ERR_FAIL_V_MSG(false, "OpenXR: Failed to parse GLTF data."); + } + + gltf_document = new_gltf_document; + gltf_state = new_state; + return true; +} + +Node3D *OpenXRRenderModelData::new_scene_instance() { + ERR_FAIL_COND_V(gltf_document.is_null(), nullptr); + ERR_FAIL_COND_V(gltf_state.is_null(), nullptr); + + return Object::cast_to(gltf_document->generate_scene(gltf_state)); +} + +void OpenXRRenderModelData::set_node_names(const PackedStringArray &p_node_names) { + node_names = p_node_names; +} + +PackedStringArray OpenXRRenderModelData::get_node_names() const { + return node_names; +} + +const String OpenXRRenderModelData::get_node_name(uint32_t p_node_index) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_node_index, node_names.size(), String()); + + return node_names[p_node_index]; +} + +OpenXRRenderModelData::OpenXRRenderModelData() { +} + +OpenXRRenderModelData::~OpenXRRenderModelData() { +} diff --git a/modules/openxr/extensions/openxr_render_model_extension.h b/modules/openxr/extensions/openxr_render_model_extension.h new file mode 100644 index 00000000000..d5bc446847a --- /dev/null +++ b/modules/openxr/extensions/openxr_render_model_extension.h @@ -0,0 +1,168 @@ +/**************************************************************************/ +/* openxr_render_model_extension.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "../openxr_uuid.h" +#include "../util.h" +#include "core/templates/rid_owner.h" +#include "modules/gltf/gltf_document.h" +#include "openxr_extension_wrapper.h" +#include "scene/3d/node_3d.h" +#include "servers/xr/xr_pose.h" + +#include + +class OpenXRRenderModelData : public RefCounted { + GDCLASS(OpenXRRenderModelData, RefCounted); + +private: + Ref gltf_document; + Ref gltf_state; + PackedStringArray node_names; + +public: + Ref get_gltf_state() { return gltf_state; } + + bool parse_gltf_document(const PackedByteArray &p_bytes); + Node3D *new_scene_instance(); + + void set_node_names(const PackedStringArray &p_node_names); + PackedStringArray get_node_names() const; + const String get_node_name(uint32_t p_node_index) const; + + OpenXRRenderModelData(); + ~OpenXRRenderModelData(); +}; + +class OpenXRRenderModelExtension : public OpenXRExtensionWrapper { + GDCLASS(OpenXRRenderModelExtension, OpenXRExtensionWrapper); + +protected: + static void _bind_methods(); + +public: + static OpenXRRenderModelExtension *get_singleton(); + + OpenXRRenderModelExtension(); + virtual ~OpenXRRenderModelExtension() override; + + virtual HashMap get_requested_extensions() override; + + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void on_session_created(const XrSession p_session) override; + virtual void on_instance_destroyed() override; + virtual void on_session_destroyed() override; + + virtual bool on_event_polled(const XrEventDataBuffer &event) override; + virtual void on_sync_actions() override; + + bool is_active() const; + + // Render model. + bool has_render_model(RID p_render_model) const; + RID render_model_create(XrRenderModelIdEXT p_render_model_id); + void render_model_destroy(RID p_render_model); + + TypedArray render_model_get_all(); + Node3D *render_model_new_scene_instance(RID p_render_model) const; + PackedStringArray render_model_get_subaction_paths(RID p_render_model); + XrPath render_model_get_top_level_path(RID p_render_model) const; + String render_model_get_top_level_path_as_string(RID p_render_model) const; + XRPose::TrackingConfidence render_model_get_confidence(RID p_render_model) const; + Transform3D render_model_get_root_transform(RID p_render_model) const; + uint32_t render_model_get_animatable_node_count(RID p_render_model) const; + String render_model_get_animatable_node_name(RID p_render_model, uint32_t p_index) const; + bool render_model_is_animatable_node_visible(RID p_render_model, uint32_t p_index) const; + Transform3D render_model_get_animatable_node_transform(RID p_render_model, uint32_t p_index) const; + +private: + static OpenXRRenderModelExtension *singleton; + + // Related extensions. + bool uuid_ext = false; + bool render_model_ext = false; + bool interaction_render_model_ext = false; + + // XrSync status + bool xr_sync_has_run = false; + + // Interaction data. + bool _interaction_data_dirty = true; + HashMap interaction_render_models; + + void _clear_interaction_data(); + bool _update_interaction_data(); + + // Render model. + Vector toplevel_paths; + + struct RenderModel { + XrRenderModelIdEXT xr_render_model_id = XR_NULL_RENDER_MODEL_ID_EXT; + XrRenderModelEXT xr_render_model = XR_NULL_HANDLE; + uint32_t animatable_node_count = 0; + Ref render_model_data; + XrSpace xr_space = XR_NULL_HANDLE; + XRPose::TrackingConfidence confidence = XRPose::TrackingConfidence::XR_TRACKING_CONFIDENCE_NONE; + Transform3D root_transform; + LocalVector node_states; + XrPath top_level_path = XR_NULL_PATH; + }; + + mutable RID_Owner render_model_owner; + + // GLTF asset cache + HashMap, HashMapHasherXrUuidEXT> render_model_data_cache; + + Ref _get_render_model_data(XrUuidEXT p_cache_id, uint32_t p_animatable_node_count); + Ref _load_asset(XrRenderModelAssetEXT p_asset, uint32_t p_animatable_node_count); + void _clear_render_model_data(); + + // GDScript/GDExtension passthroughs + RID _render_model_create(uint64_t p_render_model_id); + + // OpenXR API call wrappers + EXT_PROTO_XRRESULT_FUNC3(xrCreateRenderModelEXT, (XrSession), session, (const XrRenderModelCreateInfoEXT *), createInfo, (XrRenderModelEXT *), renderModel); + EXT_PROTO_XRRESULT_FUNC1(xrDestroyRenderModelEXT, (XrRenderModelEXT), renderModel); + EXT_PROTO_XRRESULT_FUNC3(xrGetRenderModelPropertiesEXT, (XrRenderModelEXT), renderModel, (const XrRenderModelPropertiesGetInfoEXT *), getInfo, (XrRenderModelPropertiesEXT *), properties); + EXT_PROTO_XRRESULT_FUNC3(xrCreateRenderModelSpaceEXT, (XrSession), session, (const XrRenderModelSpaceCreateInfoEXT *), createInfo, (XrSpace *), space); + EXT_PROTO_XRRESULT_FUNC3(xrCreateRenderModelAssetEXT, (XrSession), session, (const XrRenderModelAssetCreateInfoEXT *), createInfo, (XrRenderModelAssetEXT *), asset); + EXT_PROTO_XRRESULT_FUNC1(xrDestroyRenderModelAssetEXT, (XrRenderModelAssetEXT), asset); + EXT_PROTO_XRRESULT_FUNC3(xrGetRenderModelAssetDataEXT, (XrRenderModelAssetEXT), asset, (const XrRenderModelAssetDataGetInfoEXT *), getInfo, (XrRenderModelAssetDataEXT *), buffer); + EXT_PROTO_XRRESULT_FUNC3(xrGetRenderModelAssetPropertiesEXT, (XrRenderModelAssetEXT), asset, (const XrRenderModelAssetPropertiesGetInfoEXT *), getInfo, (XrRenderModelAssetPropertiesEXT *), properties); + EXT_PROTO_XRRESULT_FUNC3(xrGetRenderModelStateEXT, (XrRenderModelEXT), renderModel, (const XrRenderModelStateGetInfoEXT *), getInfo, (XrRenderModelStateEXT *), state); + EXT_PROTO_XRRESULT_FUNC5(xrEnumerateInteractionRenderModelIdsEXT, (XrSession), session, (const XrInteractionRenderModelIdsEnumerateInfoEXT *), getInfo, (uint32_t), renderModelIdCapacityInput, (uint32_t *), renderModelIdCountOutput, (XrRenderModelIdEXT *), renderModelIds); + EXT_PROTO_XRRESULT_FUNC5(xrEnumerateRenderModelSubactionPathsEXT, (XrRenderModelEXT), renderModel, (const XrInteractionRenderModelSubactionPathInfoEXT *), info, (uint32_t), pathCapacityInput, (uint32_t *), pathCountOutput, (XrPath *), paths); + EXT_PROTO_XRRESULT_FUNC3(xrGetRenderModelPoseTopLevelUserPathEXT, (XrRenderModelEXT), renderModel, (const XrInteractionRenderModelTopLevelUserPathGetInfoEXT *), info, (XrPath *), topLevelUserPath); + + EXT_PROTO_XRRESULT_FUNC4(xrLocateSpace, (XrSpace), space, (XrSpace), baseSpace, (XrTime), time, (XrSpaceLocation *), location); + EXT_PROTO_XRRESULT_FUNC1(xrDestroySpace, (XrSpace), space); + EXT_PROTO_XRRESULT_FUNC5(xrPathToString, (XrInstance), instance, (XrPath), path, (uint32_t), bufferCapacityInput, (uint32_t *), bufferCountOutput, (char *), buffer); +}; diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 51a12e772cd..2a154d06b37 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -633,7 +633,7 @@ bool OpenXRAPI::create_instance() { } XrResult result = xrCreateInstance(&instance_create_info, &instance); - ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "Failed to create XR instance."); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "Failed to create XR instance [" + get_error_string(result) + "]."); // from this point on we can use get_error_string to get more info about our errors... @@ -2904,6 +2904,20 @@ XrPath OpenXRAPI::get_xr_path(const String &p_path) { return path; } +String OpenXRAPI::get_xr_path_name(const XrPath &p_path) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, String()); + + uint32_t size = 0; + char path_name[XR_MAX_PATH_LENGTH]; + + XrResult result = xrPathToString(instance, p_path, XR_MAX_PATH_LENGTH, &size, path_name); + if (XR_FAILED(result)) { + ERR_FAIL_V_MSG(String(), "OpenXR: failed to get name for a path! [" + get_error_string(result) + "]"); + } + + return String(path_name); +} + RID OpenXRAPI::get_tracker_rid(XrPath p_path) { for (const RID &tracker_rid : tracker_owner.get_owned_list()) { Tracker *tracker = tracker_owner.get_or_null(tracker_rid); @@ -3458,6 +3472,10 @@ bool OpenXRAPI::sync_action_sets(const Vector p_active_sets) { return false; } + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_sync_actions(); + } + return true; } diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 0ee9d8e9cdc..74eaf5f1875 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -435,6 +435,7 @@ public: void parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); bool xr_result(XrResult result, const char *format, Array args = Array()) const; XrPath get_xr_path(const String &p_path); + String get_xr_path_name(const XrPath &p_path); bool is_top_level_path_supported(const String &p_toplevel_path); bool is_interaction_profile_supported(const String &p_ip_path); bool interaction_profile_supports_io_path(const String &p_ip_path, const String &p_io_path); diff --git a/modules/openxr/openxr_uuid.h b/modules/openxr/openxr_uuid.h new file mode 100644 index 00000000000..71eb01831ae --- /dev/null +++ b/modules/openxr/openxr_uuid.h @@ -0,0 +1,52 @@ +/**************************************************************************/ +/* openxr_uuid.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +// Godot helper functions for OpenXR XrUuidExt data type +#include "core/templates/hashfuncs.h" + +#include + +struct HashMapHasherXrUuidEXT { + static _FORCE_INLINE_ uint32_t hash(const XrUuidEXT &p_uuid) { return hash_murmur3_buffer(p_uuid.data, XR_UUID_SIZE_EXT); } +}; + +template <> +struct HashMapComparatorDefault { + static bool compare(const XrUuidEXT &p_lhs, const XrUuidEXT &p_rhs) { + for (int i = 0; i < XR_UUID_SIZE_EXT; i++) { + if (p_lhs.data[i] != p_rhs.data[i]) { + return false; + } + } + return true; + } +}; diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index 5cf3699cd9a..d168da1bab4 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -48,6 +48,8 @@ #include "scene/openxr_composition_layer_cylinder.h" #include "scene/openxr_composition_layer_equirect.h" #include "scene/openxr_composition_layer_quad.h" +#include "scene/openxr_render_model.h" +#include "scene/openxr_render_model_manager.h" #include "scene/openxr_visibility_mask.h" #include "extensions/openxr_composition_layer_depth_extension.h" @@ -69,6 +71,7 @@ #include "extensions/openxr_palm_pose_extension.h" #include "extensions/openxr_performance_settings_extension.h" #include "extensions/openxr_pico_controller_extension.h" +#include "extensions/openxr_render_model_extension.h" #include "extensions/openxr_valve_analog_threshold_extension.h" #include "extensions/openxr_visibility_mask_extension.h" #include "extensions/openxr_wmr_controller_extension.h" @@ -122,6 +125,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { GDREGISTER_ABSTRACT_CLASS(OpenXRFutureResult); // Declared abstract, should never be instantiated by a user (Q or should this be internal?) GDREGISTER_CLASS(OpenXRFutureExtension); GDREGISTER_CLASS(OpenXRAPIExtension); + GDREGISTER_CLASS(OpenXRRenderModelExtension); // Note, we're not registering all wrapper classes here, there is no point in exposing them // if there isn't specific logic to expose. @@ -159,6 +163,11 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { OpenXRAPI::register_extension_wrapper(future_extension); Engine::get_singleton()->add_singleton(Engine::Singleton("OpenXRFutureExtension", future_extension)); + // Register render model extension as a singleton. + OpenXRRenderModelExtension *render_model_extension = memnew(OpenXRRenderModelExtension); + OpenXRAPI::register_extension_wrapper(render_model_extension); + Engine::get_singleton()->add_singleton(Engine::Singleton("OpenXRRenderModelExtension", render_model_extension)); + // register gated extensions if (int(GLOBAL_GET("xr/openxr/extensions/debug_utils")) > 0) { OpenXRAPI::register_extension_wrapper(memnew(OpenXRDebugUtilsExtension)); @@ -234,6 +243,8 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { #endif GDREGISTER_CLASS(OpenXRVisibilityMask); + GDREGISTER_CLASS(OpenXRRenderModel); + GDREGISTER_CLASS(OpenXRRenderModelManager); XRServer *xr_server = XRServer::get_singleton(); if (xr_server) { diff --git a/modules/openxr/scene/openxr_render_model.cpp b/modules/openxr/scene/openxr_render_model.cpp new file mode 100644 index 00000000000..8a4a25a57b4 --- /dev/null +++ b/modules/openxr/scene/openxr_render_model.cpp @@ -0,0 +1,168 @@ +/**************************************************************************/ +/* openxr_render_model.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "openxr_render_model.h" + +#include "../extensions/openxr_render_model_extension.h" +#include "core/config/project_settings.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/xr/xr_nodes.h" +#include "scene/resources/3d/primitive_meshes.h" + +void OpenXRRenderModel::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_top_level_path"), &OpenXRRenderModel::get_top_level_path); + + ClassDB::bind_method(D_METHOD("get_render_model"), &OpenXRRenderModel::get_render_model); + ClassDB::bind_method(D_METHOD("set_render_model", "render_model"), &OpenXRRenderModel::set_render_model); + ADD_PROPERTY(PropertyInfo(Variant::RID, "render_model"), "set_render_model", "get_render_model"); + + ADD_SIGNAL(MethodInfo("render_model_top_level_path_changed")); +} + +void OpenXRRenderModel::_load_render_model_scene() { + OpenXRRenderModelExtension *render_model_extension = OpenXRRenderModelExtension::get_singleton(); + ERR_FAIL_NULL(render_model_extension); + ERR_FAIL_COND(render_model.is_null()); + + scene = render_model_extension->render_model_new_scene_instance(render_model); + if (scene) { + // Get and cache our animatable nodes. + animatable_nodes.clear(); + uint32_t count = render_model_extension->render_model_get_animatable_node_count(render_model); + for (uint32_t i = 0; i < count; i++) { + String node_name = render_model_extension->render_model_get_animatable_node_name(render_model, i); + if (!node_name.is_empty()) { + Node3D *child = Object::cast_to(scene->find_child(node_name)); + if (child) { + animatable_nodes[node_name] = child; + } + } + } + + // Now add to scene. + add_child(scene); + } +} + +void OpenXRRenderModel::_on_render_model_top_level_path_changed(RID p_render_model) { + if (render_model == p_render_model) { + emit_signal("render_model_top_level_path_changed"); + } +} + +void OpenXRRenderModel::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + OpenXRRenderModelExtension *render_model_extension = OpenXRRenderModelExtension::get_singleton(); + ERR_FAIL_NULL(render_model_extension); + if (render_model.is_valid()) { + _load_render_model_scene(); + } + + set_process_internal(true); + render_model_extension->connect("render_model_top_level_path_changed", callable_mp(this, &OpenXRRenderModel::_on_render_model_top_level_path_changed)); + } break; + case NOTIFICATION_EXIT_TREE: { + set_process_internal(false); + + if (scene) { + animatable_nodes.clear(); + + remove_child(scene); + scene->queue_free(); + scene = nullptr; + } + + OpenXRRenderModelExtension *render_model_extension = OpenXRRenderModelExtension::get_singleton(); + if (render_model_extension) { + render_model_extension->disconnect("render_model_top_level_path_changed", callable_mp(this, &OpenXRRenderModel::_on_render_model_top_level_path_changed)); + } + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + if (render_model.is_valid()) { + OpenXRRenderModelExtension *render_model_extension = OpenXRRenderModelExtension::get_singleton(); + ERR_FAIL_NULL(render_model_extension); + + if (render_model_extension->render_model_get_confidence(render_model) != XRPose::TrackingConfidence::XR_TRACKING_CONFIDENCE_NONE) { + set_transform(render_model_extension->render_model_get_root_transform(render_model)); + + if (scene) { + uint32_t count = render_model_extension->render_model_get_animatable_node_count(render_model); + for (uint32_t i = 0; i < count; i++) { + String node_name = render_model_extension->render_model_get_animatable_node_name(render_model, i); + if (!node_name.is_empty() && animatable_nodes.has(node_name)) { + Node3D *child = animatable_nodes[node_name]; + child->set_visible(render_model_extension->render_model_is_animatable_node_visible(render_model, i)); + child->set_transform(render_model_extension->render_model_get_animatable_node_transform(render_model, i)); + } + } + } + } + } + } break; + } +} + +String OpenXRRenderModel::get_top_level_path() const { + String ret; + + OpenXRRenderModelExtension *render_model_extension = OpenXRRenderModelExtension::get_singleton(); + if (render_model.is_valid() && render_model_extension) { + ret = render_model_extension->render_model_get_top_level_path_as_string(render_model); + } + + return ret; +} + +PackedStringArray OpenXRRenderModel::get_configuration_warnings() const { + PackedStringArray warnings; + + Node *parent = get_parent(); + if (!parent->is_class("XROrigin3D") && !parent->is_class("OpenXRRenderModelManager")) { + warnings.push_back("This node must be a child of either a XROrigin3D or OpenXRRenderModelManager node!"); + } + + if (!GLOBAL_GET("xr/openxr/extensions/render_model")) { + warnings.push_back("The render model extension is not enabled in project settings!"); + } + + return warnings; +} + +RID OpenXRRenderModel::get_render_model() const { + return render_model; +} + +void OpenXRRenderModel::set_render_model(RID p_render_model) { + render_model = p_render_model; + if (is_inside_tree() && render_model.is_valid()) { + _load_render_model_scene(); + } +} diff --git a/modules/openxr/scene/openxr_render_model.h b/modules/openxr/scene/openxr_render_model.h new file mode 100644 index 00000000000..a0f74159b71 --- /dev/null +++ b/modules/openxr/scene/openxr_render_model.h @@ -0,0 +1,60 @@ +/**************************************************************************/ +/* openxr_render_model.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "scene/3d/node_3d.h" + +#include + +class OpenXRRenderModel : public Node3D { + GDCLASS(OpenXRRenderModel, Node3D); + +private: + RID render_model; + Node3D *scene = nullptr; + HashMap animatable_nodes; + + void _load_render_model_scene(); + void _on_render_model_top_level_path_changed(RID p_render_model); + +protected: + static void _bind_methods(); + + void _notification(int p_what); + +public: + virtual PackedStringArray get_configuration_warnings() const override; + + RID get_render_model() const; + void set_render_model(RID p_render_model); + + String get_top_level_path() const; +}; diff --git a/modules/openxr/scene/openxr_render_model_manager.cpp b/modules/openxr/scene/openxr_render_model_manager.cpp new file mode 100644 index 00000000000..ae1d929bf0d --- /dev/null +++ b/modules/openxr/scene/openxr_render_model_manager.cpp @@ -0,0 +1,284 @@ +/**************************************************************************/ +/* openxr_render_model_manager.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "openxr_render_model_manager.h" + +#include "../extensions/openxr_render_model_extension.h" +#include "../openxr_api.h" +#include "core/config/project_settings.h" +#include "scene/3d/xr/xr_nodes.h" +#include "servers/xr_server.h" + +void OpenXRRenderModelManager::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_tracker"), &OpenXRRenderModelManager::get_tracker); + ClassDB::bind_method(D_METHOD("set_tracker", "tracker"), &OpenXRRenderModelManager::set_tracker); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tracker", PROPERTY_HINT_ENUM, "Any,None set,Left Hand,Right Hand"), "set_tracker", "get_tracker"); + + ClassDB::bind_method(D_METHOD("get_make_local_to_pose"), &OpenXRRenderModelManager::get_make_local_to_pose); + ClassDB::bind_method(D_METHOD("set_make_local_to_pose", "make_local_to_pose"), &OpenXRRenderModelManager::set_make_local_to_pose); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "make_local_to_pose", PROPERTY_HINT_ENUM_SUGGESTION, "aim,grip"), "set_make_local_to_pose", "get_make_local_to_pose"); + + ADD_SIGNAL(MethodInfo("render_model_added", PropertyInfo(Variant::OBJECT, "render_model", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRRenderModel"))); + ADD_SIGNAL(MethodInfo("render_model_removed", PropertyInfo(Variant::OBJECT, "render_model", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRRenderModel"))); + + BIND_ENUM_CONSTANT(RENDER_MODEL_TRACKER_ANY); + BIND_ENUM_CONSTANT(RENDER_MODEL_TRACKER_NONE_SET); + BIND_ENUM_CONSTANT(RENDER_MODEL_TRACKER_LEFT_HAND); + BIND_ENUM_CONSTANT(RENDER_MODEL_TRACKER_RIGHT_HAND); +} + +bool OpenXRRenderModelManager::_has_filters() { + return tracker != 0; +} + +void OpenXRRenderModelManager::_update_models() { + OpenXRRenderModelExtension *render_model_extension = OpenXRRenderModelExtension::get_singleton(); + ERR_FAIL_NULL(render_model_extension); + + // Make a copy of our current models. + HashMap org_render_models = render_models; + + // Loop through our interaction data so we add new entries. + TypedArray render_model_rids = render_model_extension->render_model_get_all(); + for (const RID rid : render_model_rids) { + bool filter = false; + + if (tracker != 0) { + XrPath model_path = render_model_extension->render_model_get_top_level_path(rid); + if (model_path != xr_path) { + // ignore this. + filter = true; + } + } + + if (!filter) { + if (render_models.has(rid)) { + org_render_models.erase(rid); + } else { + // Create our container node before adding our first render model. + if (container == nullptr) { + container = memnew(Node3D); + add_child(container); + } + + OpenXRRenderModel *render_model = memnew(OpenXRRenderModel); + render_model->set_render_model(rid); + container->add_child(render_model); + render_models[rid] = render_model; + + emit_signal(SNAME("render_model_added"), render_model); + } + } + } + + // Remove models we no longer need. + for (const KeyValue &e : org_render_models) { + // We sent this just before removing. + emit_signal(SNAME("render_model_removed"), e.value); + + if (container) { + container->remove_child(e.value); + } + e.value->queue_free(); + render_models.erase(e.key); + } + + is_dirty = false; +} + +void OpenXRRenderModelManager::_on_render_model_added(RID p_render_model) { + if (_has_filters()) { + // We'll update this in internal process. + is_dirty = true; + } else { + // No filters? Do this right away. + _update_models(); + } +} + +void OpenXRRenderModelManager::_on_render_model_removed(RID p_render_model) { + if (_has_filters()) { + // We'll update this in internal process. + is_dirty = true; + } else { + // No filters? Do this right away. + _update_models(); + } +} + +void OpenXRRenderModelManager::_on_render_model_top_level_path_changed(RID p_path) { + if (_has_filters()) { + // We'll update this in internal process. + is_dirty = true; + } +} + +void OpenXRRenderModelManager::_notification(int p_what) { + // Do not run in editor! + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + + OpenXRRenderModelExtension *render_model_extension = OpenXRRenderModelExtension::get_singleton(); + ERR_FAIL_NULL(render_model_extension); + if (!render_model_extension->is_active()) { + return; + } + + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + _update_models(); + + render_model_extension->connect(SNAME("render_model_added"), callable_mp(this, &OpenXRRenderModelManager::_on_render_model_added)); + render_model_extension->connect(SNAME("render_model_removed"), callable_mp(this, &OpenXRRenderModelManager::_on_render_model_removed)); + render_model_extension->connect(SNAME("render_model_top_level_path_changed"), callable_mp(this, &OpenXRRenderModelManager::_on_render_model_top_level_path_changed)); + + if (_has_filters()) { + set_process_internal(true); + } + } break; + case NOTIFICATION_EXIT_TREE: { + render_model_extension->disconnect(SNAME("render_model_added"), callable_mp(this, &OpenXRRenderModelManager::_on_render_model_added)); + render_model_extension->disconnect(SNAME("render_model_removed"), callable_mp(this, &OpenXRRenderModelManager::_on_render_model_removed)); + render_model_extension->disconnect(SNAME("render_model_top_level_path_changed"), callable_mp(this, &OpenXRRenderModelManager::_on_render_model_top_level_path_changed)); + + set_process_internal(false); + is_dirty = false; + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + if (is_dirty) { + _update_models(); + } + + if (positional_tracker.is_valid() && !make_local_to_pose.is_empty() && container) { + Ref pose = positional_tracker->get_pose(make_local_to_pose); + if (pose.is_valid()) { + container->set_transform(pose->get_adjusted_transform().affine_inverse()); + } else { + container->set_transform(Transform3D()); + } + } + + if (!_has_filters()) { + // No need to keep calling this. + set_process_internal(false); + } + } + } +} + +PackedStringArray OpenXRRenderModelManager::get_configuration_warnings() const { + PackedStringArray warnings; + + XROrigin3D *parent = nullptr; + if (tracker == 0 || tracker == 1) { + if (!make_local_to_pose.is_empty()) { + warnings.push_back("Must specify a tracker to make node local to pose."); + } + + parent = Object::cast_to(get_parent()); + } else { + Node *node = get_parent(); + while (!parent && node) { + parent = Object::cast_to(node); + + node = node->get_parent(); + } + } + if (!parent) { + warnings.push_back("This node must be a child of an XROrigin3D node!"); + } + + if (!GLOBAL_GET("xr/openxr/extensions/render_model")) { + warnings.push_back("The render model extension is not enabled in project settings!"); + } + + return warnings; +} + +void OpenXRRenderModelManager::set_tracker(RenderModelTracker p_tracker) { + if (tracker != p_tracker) { + tracker = p_tracker; + is_dirty = true; + + if (tracker == RENDER_MODEL_TRACKER_ANY || tracker == RENDER_MODEL_TRACKER_NONE_SET) { + xr_path = XR_NULL_PATH; + } else if (!Engine::get_singleton()->is_editor_hint()) { + XRServer *xr_server = XRServer::get_singleton(); + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + if (openxr_api && xr_server) { + String toplevel_path; + String tracker_name; + if (tracker == RENDER_MODEL_TRACKER_LEFT_HAND) { + tracker_name = "left_hand"; + toplevel_path = "/user/hand/left"; + } else if (tracker == RENDER_MODEL_TRACKER_RIGHT_HAND) { + tracker_name = "right_hand"; + toplevel_path = "/user/hand/right"; + } else { + ERR_FAIL_MSG("Unsupported tracker value set."); + } + + positional_tracker = xr_server->get_tracker(tracker_name); + if (positional_tracker.is_null()) { + WARN_PRINT("OpenXR: Can't find tracker " + tracker_name); + } + + xr_path = openxr_api->get_xr_path(toplevel_path); + if (xr_path == XR_NULL_PATH) { + WARN_PRINT("OpenXR: Can't find path for " + toplevel_path); + } + } + } + + // Even if we now no longer have filters, we must update at least once. + set_process_internal(true); + } +} + +OpenXRRenderModelManager::RenderModelTracker OpenXRRenderModelManager::get_tracker() const { + return tracker; +} + +void OpenXRRenderModelManager::set_make_local_to_pose(const String &p_action) { + if (make_local_to_pose != p_action) { + make_local_to_pose = p_action; + + if (container) { + // Reset just in case. It'll be set to the correct transform + // in our process if required. + container->set_transform(Transform3D()); + } + } +} + +String OpenXRRenderModelManager::get_make_local_to_pose() const { + return make_local_to_pose; +} diff --git a/modules/openxr/scene/openxr_render_model_manager.h b/modules/openxr/scene/openxr_render_model_manager.h new file mode 100644 index 00000000000..821a12a8076 --- /dev/null +++ b/modules/openxr/scene/openxr_render_model_manager.h @@ -0,0 +1,84 @@ +/**************************************************************************/ +/* openxr_render_model_manager.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "openxr_render_model.h" + +#include "scene/3d/node_3d.h" +#include "scene/resources/packed_scene.h" +#include "servers/xr/xr_positional_tracker.h" + +#include + +class OpenXRRenderModelManager : public Node3D { + GDCLASS(OpenXRRenderModelManager, Node3D); + +public: + enum RenderModelTracker { + RENDER_MODEL_TRACKER_ANY, + RENDER_MODEL_TRACKER_NONE_SET, + RENDER_MODEL_TRACKER_LEFT_HAND, + RENDER_MODEL_TRACKER_RIGHT_HAND, + }; + + virtual PackedStringArray get_configuration_warnings() const override; + + void set_tracker(RenderModelTracker p_tracker); + RenderModelTracker get_tracker() const; + + void set_make_local_to_pose(const String &p_action); + String get_make_local_to_pose() const; + +private: + HashMap render_models; + Node3D *container = nullptr; + + bool is_dirty = false; + RenderModelTracker tracker = RENDER_MODEL_TRACKER_ANY; + String make_local_to_pose; + + // cached values + Ref positional_tracker; + XrPath xr_path = XR_NULL_PATH; + + bool _has_filters(); + void _on_render_model_added(RID p_render_model); + void _on_render_model_removed(RID p_render_model); + void _on_render_model_top_level_path_changed(RID p_path); + void _update_models(); + +protected: + static void _bind_methods(); + + void _notification(int p_what); +}; + +VARIANT_ENUM_CAST(OpenXRRenderModelManager::RenderModelTracker);