| 1 | /**************************************************************************/ |
| 2 | /* openxr_interface.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "openxr_interface.h" |
| 32 | |
| 33 | #include "core/io/resource_loader.h" |
| 34 | #include "core/io/resource_saver.h" |
| 35 | #include "servers/rendering/rendering_server_globals.h" |
| 36 | |
| 37 | #include "extensions/openxr_hand_tracking_extension.h" |
| 38 | |
| 39 | void OpenXRInterface::_bind_methods() { |
| 40 | // lifecycle signals |
| 41 | ADD_SIGNAL(MethodInfo("session_begun" )); |
| 42 | ADD_SIGNAL(MethodInfo("session_stopping" )); |
| 43 | ADD_SIGNAL(MethodInfo("session_focussed" )); |
| 44 | ADD_SIGNAL(MethodInfo("session_visible" )); |
| 45 | ADD_SIGNAL(MethodInfo("pose_recentered" )); |
| 46 | |
| 47 | // Display refresh rate |
| 48 | ClassDB::bind_method(D_METHOD("get_display_refresh_rate" ), &OpenXRInterface::get_display_refresh_rate); |
| 49 | ClassDB::bind_method(D_METHOD("set_display_refresh_rate" , "refresh_rate" ), &OpenXRInterface::set_display_refresh_rate); |
| 50 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "display_refresh_rate" ), "set_display_refresh_rate" , "get_display_refresh_rate" ); |
| 51 | |
| 52 | // Render Target size multiplier |
| 53 | ClassDB::bind_method(D_METHOD("get_render_target_size_multiplier" ), &OpenXRInterface::get_render_target_size_multiplier); |
| 54 | ClassDB::bind_method(D_METHOD("set_render_target_size_multiplier" , "multiplier" ), &OpenXRInterface::set_render_target_size_multiplier); |
| 55 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "render_target_size_multiplier" ), "set_render_target_size_multiplier" , "get_render_target_size_multiplier" ); |
| 56 | |
| 57 | ClassDB::bind_method(D_METHOD("is_action_set_active" , "name" ), &OpenXRInterface::is_action_set_active); |
| 58 | ClassDB::bind_method(D_METHOD("set_action_set_active" , "name" , "active" ), &OpenXRInterface::set_action_set_active); |
| 59 | ClassDB::bind_method(D_METHOD("get_action_sets" ), &OpenXRInterface::get_action_sets); |
| 60 | |
| 61 | ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates" ), &OpenXRInterface::get_available_display_refresh_rates); |
| 62 | |
| 63 | // Hand tracking. |
| 64 | ClassDB::bind_method(D_METHOD("set_motion_range" , "hand" , "motion_range" ), &OpenXRInterface::set_motion_range); |
| 65 | ClassDB::bind_method(D_METHOD("get_motion_range" , "hand" ), &OpenXRInterface::get_motion_range); |
| 66 | |
| 67 | ClassDB::bind_method(D_METHOD("get_hand_joint_rotation" , "hand" , "joint" ), &OpenXRInterface::get_hand_joint_rotation); |
| 68 | ClassDB::bind_method(D_METHOD("get_hand_joint_position" , "hand" , "joint" ), &OpenXRInterface::get_hand_joint_position); |
| 69 | ClassDB::bind_method(D_METHOD("get_hand_joint_radius" , "hand" , "joint" ), &OpenXRInterface::get_hand_joint_radius); |
| 70 | |
| 71 | ClassDB::bind_method(D_METHOD("get_hand_joint_linear_velocity" , "hand" , "joint" ), &OpenXRInterface::get_hand_joint_linear_velocity); |
| 72 | ClassDB::bind_method(D_METHOD("get_hand_joint_angular_velocity" , "hand" , "joint" ), &OpenXRInterface::get_hand_joint_angular_velocity); |
| 73 | |
| 74 | BIND_ENUM_CONSTANT(HAND_LEFT); |
| 75 | BIND_ENUM_CONSTANT(HAND_RIGHT); |
| 76 | BIND_ENUM_CONSTANT(HAND_MAX); |
| 77 | |
| 78 | BIND_ENUM_CONSTANT(HAND_MOTION_RANGE_UNOBSTRUCTED); |
| 79 | BIND_ENUM_CONSTANT(HAND_MOTION_RANGE_CONFORM_TO_CONTROLLER); |
| 80 | BIND_ENUM_CONSTANT(HAND_MOTION_RANGE_MAX); |
| 81 | |
| 82 | BIND_ENUM_CONSTANT(HAND_JOINT_PALM); |
| 83 | BIND_ENUM_CONSTANT(HAND_JOINT_WRIST); |
| 84 | BIND_ENUM_CONSTANT(HAND_JOINT_THUMB_METACARPAL); |
| 85 | BIND_ENUM_CONSTANT(HAND_JOINT_THUMB_PROXIMAL); |
| 86 | BIND_ENUM_CONSTANT(HAND_JOINT_THUMB_DISTAL); |
| 87 | BIND_ENUM_CONSTANT(HAND_JOINT_THUMB_TIP); |
| 88 | BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_METACARPAL); |
| 89 | BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_PROXIMAL); |
| 90 | BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_INTERMEDIATE); |
| 91 | BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_DISTAL); |
| 92 | BIND_ENUM_CONSTANT(HAND_JOINT_INDEX_TIP); |
| 93 | BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_METACARPAL); |
| 94 | BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_PROXIMAL); |
| 95 | BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_INTERMEDIATE); |
| 96 | BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_DISTAL); |
| 97 | BIND_ENUM_CONSTANT(HAND_JOINT_MIDDLE_TIP); |
| 98 | BIND_ENUM_CONSTANT(HAND_JOINT_RING_METACARPAL); |
| 99 | BIND_ENUM_CONSTANT(HAND_JOINT_RING_PROXIMAL); |
| 100 | BIND_ENUM_CONSTANT(HAND_JOINT_RING_INTERMEDIATE); |
| 101 | BIND_ENUM_CONSTANT(HAND_JOINT_RING_DISTAL); |
| 102 | BIND_ENUM_CONSTANT(HAND_JOINT_RING_TIP); |
| 103 | BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_METACARPAL); |
| 104 | BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_PROXIMAL); |
| 105 | BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_INTERMEDIATE); |
| 106 | BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_DISTAL); |
| 107 | BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_TIP); |
| 108 | BIND_ENUM_CONSTANT(HAND_JOINT_MAX); |
| 109 | } |
| 110 | |
| 111 | StringName OpenXRInterface::get_name() const { |
| 112 | return StringName("OpenXR" ); |
| 113 | }; |
| 114 | |
| 115 | uint32_t OpenXRInterface::get_capabilities() const { |
| 116 | return XRInterface::XR_VR + XRInterface::XR_STEREO; |
| 117 | }; |
| 118 | |
| 119 | PackedStringArray OpenXRInterface::get_suggested_tracker_names() const { |
| 120 | // These are hardcoded in OpenXR, note that they will only be available if added to our action map |
| 121 | |
| 122 | PackedStringArray arr = { |
| 123 | "left_hand" , // /user/hand/left is mapped to our defaults |
| 124 | "right_hand" , // /user/hand/right is mapped to our defaults |
| 125 | "/user/treadmill" , |
| 126 | |
| 127 | // Even though these are only available if you have the tracker extension, |
| 128 | // we add these as we may be deploying on a different platform than our |
| 129 | // editor is running on. |
| 130 | "/user/vive_tracker_htcx/role/handheld_object" , |
| 131 | "/user/vive_tracker_htcx/role/left_foot" , |
| 132 | "/user/vive_tracker_htcx/role/right_foot" , |
| 133 | "/user/vive_tracker_htcx/role/left_shoulder" , |
| 134 | "/user/vive_tracker_htcx/role/right_shoulder" , |
| 135 | "/user/vive_tracker_htcx/role/left_elbow" , |
| 136 | "/user/vive_tracker_htcx/role/right_elbow" , |
| 137 | "/user/vive_tracker_htcx/role/left_knee" , |
| 138 | "/user/vive_tracker_htcx/role/right_knee" , |
| 139 | "/user/vive_tracker_htcx/role/waist" , |
| 140 | "/user/vive_tracker_htcx/role/chest" , |
| 141 | "/user/vive_tracker_htcx/role/camera" , |
| 142 | "/user/vive_tracker_htcx/role/keyboard" |
| 143 | }; |
| 144 | |
| 145 | return arr; |
| 146 | } |
| 147 | |
| 148 | XRInterface::TrackingStatus OpenXRInterface::get_tracking_status() const { |
| 149 | return tracking_state; |
| 150 | } |
| 151 | |
| 152 | void OpenXRInterface::_load_action_map() { |
| 153 | ERR_FAIL_NULL(openxr_api); |
| 154 | |
| 155 | // This may seem a bit duplicitous to a little bit of background info here. |
| 156 | // OpenXRActionMap (with all its sub resource classes) is a class that allows us to configure and store an action map in. |
| 157 | // This gives the user the ability to edit the action map in a UI and customize the actions. |
| 158 | // OpenXR however requires us to submit an action map and it takes over from that point and we can no longer change it. |
| 159 | // This system does that push and we store the info needed to then work with this action map going forward. |
| 160 | |
| 161 | // Within our openxr device we maintain a number of classes that wrap the relevant OpenXR objects for this. |
| 162 | // Within OpenXRInterface we have a few internal classes that keep track of what we've created. |
| 163 | // This allow us to process the relevant actions each frame. |
| 164 | |
| 165 | // just in case clean up |
| 166 | free_trackers(); |
| 167 | free_interaction_profiles(); |
| 168 | free_action_sets(); |
| 169 | |
| 170 | Ref<OpenXRActionMap> action_map; |
| 171 | if (Engine::get_singleton()->is_editor_hint()) { |
| 172 | #ifdef TOOLS_ENABLED |
| 173 | action_map.instantiate(); |
| 174 | action_map->create_editor_action_sets(); |
| 175 | #endif |
| 176 | } else { |
| 177 | String default_tres_name = openxr_api->get_default_action_map_resource_name(); |
| 178 | |
| 179 | // Check if we can load our default |
| 180 | if (ResourceLoader::exists(default_tres_name)) { |
| 181 | action_map = ResourceLoader::load(default_tres_name); |
| 182 | } |
| 183 | |
| 184 | // Check if we need to create default action set |
| 185 | if (action_map.is_null()) { |
| 186 | action_map.instantiate(); |
| 187 | action_map->create_default_action_sets(); |
| 188 | #ifdef TOOLS_ENABLED |
| 189 | // Save our action sets so our user can |
| 190 | action_map->set_path(default_tres_name, true); |
| 191 | ResourceSaver::save(action_map, default_tres_name); |
| 192 | #endif |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | // process our action map |
| 197 | if (action_map.is_valid()) { |
| 198 | HashMap<Ref<OpenXRAction>, Action *> xr_actions; |
| 199 | |
| 200 | Array action_set_array = action_map->get_action_sets(); |
| 201 | for (int i = 0; i < action_set_array.size(); i++) { |
| 202 | // Create our action set |
| 203 | Ref<OpenXRActionSet> xr_action_set = action_set_array[i]; |
| 204 | ActionSet *action_set = create_action_set(xr_action_set->get_name(), xr_action_set->get_localized_name(), xr_action_set->get_priority()); |
| 205 | if (!action_set) { |
| 206 | continue; |
| 207 | } |
| 208 | |
| 209 | // Now create our actions for these |
| 210 | Array actions = xr_action_set->get_actions(); |
| 211 | for (int j = 0; j < actions.size(); j++) { |
| 212 | Ref<OpenXRAction> xr_action = actions[j]; |
| 213 | |
| 214 | PackedStringArray toplevel_paths = xr_action->get_toplevel_paths(); |
| 215 | Vector<Tracker *> trackers_for_action; |
| 216 | |
| 217 | for (int k = 0; k < toplevel_paths.size(); k++) { |
| 218 | // Only check for our tracker if our path is supported. |
| 219 | if (openxr_api->is_top_level_path_supported(toplevel_paths[k])) { |
| 220 | Tracker *tracker = find_tracker(toplevel_paths[k], true); |
| 221 | if (tracker) { |
| 222 | trackers_for_action.push_back(tracker); |
| 223 | } |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | // Only add our action if we have at least one valid toplevel path |
| 228 | if (trackers_for_action.size() > 0) { |
| 229 | Action *action = create_action(action_set, xr_action->get_name(), xr_action->get_localized_name(), xr_action->get_action_type(), trackers_for_action); |
| 230 | if (action) { |
| 231 | // add this to our map for creating our interaction profiles |
| 232 | xr_actions[xr_action] = action; |
| 233 | } |
| 234 | } |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | // now do our suggestions |
| 239 | Array interaction_profile_array = action_map->get_interaction_profiles(); |
| 240 | for (int i = 0; i < interaction_profile_array.size(); i++) { |
| 241 | Ref<OpenXRInteractionProfile> xr_interaction_profile = interaction_profile_array[i]; |
| 242 | |
| 243 | // Note, we can only have one entry per interaction profile so if it already exists we clear it out |
| 244 | RID ip = openxr_api->interaction_profile_create(xr_interaction_profile->get_interaction_profile_path()); |
| 245 | if (ip.is_valid()) { |
| 246 | openxr_api->interaction_profile_clear_bindings(ip); |
| 247 | |
| 248 | Array xr_bindings = xr_interaction_profile->get_bindings(); |
| 249 | for (int j = 0; j < xr_bindings.size(); j++) { |
| 250 | Ref<OpenXRIPBinding> xr_binding = xr_bindings[j]; |
| 251 | Ref<OpenXRAction> xr_action = xr_binding->get_action(); |
| 252 | |
| 253 | Action *action = nullptr; |
| 254 | if (xr_actions.has(xr_action)) { |
| 255 | action = xr_actions[xr_action]; |
| 256 | } else { |
| 257 | print_line("Action " , xr_action->get_name(), " isn't part of an action set!" ); |
| 258 | continue; |
| 259 | } |
| 260 | |
| 261 | PackedStringArray paths = xr_binding->get_paths(); |
| 262 | for (int k = 0; k < paths.size(); k++) { |
| 263 | openxr_api->interaction_profile_add_binding(ip, action->action_rid, paths[k]); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | // Now submit our suggestions |
| 268 | openxr_api->interaction_profile_suggest_bindings(ip); |
| 269 | |
| 270 | // And record it in our array so we can clean it up later on |
| 271 | if (interaction_profile_array.has(ip)) { |
| 272 | interaction_profile_array.push_back(ip); |
| 273 | } |
| 274 | } |
| 275 | } |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | OpenXRInterface::ActionSet *OpenXRInterface::create_action_set(const String &p_action_set_name, const String &p_localized_name, const int p_priority) { |
| 280 | ERR_FAIL_NULL_V(openxr_api, nullptr); |
| 281 | |
| 282 | // find if it already exists |
| 283 | for (int i = 0; i < action_sets.size(); i++) { |
| 284 | if (action_sets[i]->action_set_name == p_action_set_name) { |
| 285 | // already exists in this set |
| 286 | return nullptr; |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | ActionSet *action_set = memnew(ActionSet); |
| 291 | action_set->action_set_name = p_action_set_name; |
| 292 | action_set->is_active = true; |
| 293 | action_set->action_set_rid = openxr_api->action_set_create(p_action_set_name, p_localized_name, p_priority); |
| 294 | action_sets.push_back(action_set); |
| 295 | |
| 296 | return action_set; |
| 297 | } |
| 298 | |
| 299 | void OpenXRInterface::free_action_sets() { |
| 300 | ERR_FAIL_NULL(openxr_api); |
| 301 | |
| 302 | for (int i = 0; i < action_sets.size(); i++) { |
| 303 | ActionSet *action_set = action_sets[i]; |
| 304 | |
| 305 | free_actions(action_set); |
| 306 | |
| 307 | openxr_api->action_set_free(action_set->action_set_rid); |
| 308 | |
| 309 | memfree(action_set); |
| 310 | } |
| 311 | action_sets.clear(); |
| 312 | } |
| 313 | |
| 314 | 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) { |
| 315 | ERR_FAIL_NULL_V(openxr_api, nullptr); |
| 316 | |
| 317 | for (int i = 0; i < p_action_set->actions.size(); i++) { |
| 318 | if (p_action_set->actions[i]->action_name == p_action_name) { |
| 319 | // already exists in this set |
| 320 | return nullptr; |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | Vector<RID> tracker_rids; |
| 325 | for (int i = 0; i < p_trackers.size(); i++) { |
| 326 | tracker_rids.push_back(p_trackers[i]->tracker_rid); |
| 327 | } |
| 328 | |
| 329 | Action *action = memnew(Action); |
| 330 | if (p_action_type == OpenXRAction::OPENXR_ACTION_POSE) { |
| 331 | // We can't have dual action names in OpenXR hence we added _pose, |
| 332 | // but default, aim and grip and default pose action names in Godot so rename them on the tracker. |
| 333 | // NOTE need to decide on whether we should keep the naming convention or rename it on Godots side |
| 334 | if (p_action_name == "default_pose" ) { |
| 335 | action->action_name = "default" ; |
| 336 | } else if (p_action_name == "aim_pose" ) { |
| 337 | action->action_name = "aim" ; |
| 338 | } else if (p_action_name == "grip_pose" ) { |
| 339 | action->action_name = "grip" ; |
| 340 | } else { |
| 341 | action->action_name = p_action_name; |
| 342 | } |
| 343 | } else { |
| 344 | action->action_name = p_action_name; |
| 345 | } |
| 346 | |
| 347 | action->action_type = p_action_type; |
| 348 | action->action_rid = openxr_api->action_create(p_action_set->action_set_rid, p_action_name, p_localized_name, p_action_type, tracker_rids); |
| 349 | p_action_set->actions.push_back(action); |
| 350 | |
| 351 | // we link our actions back to our trackers so we know which actions to check when we're processing our trackers |
| 352 | for (int i = 0; i < p_trackers.size(); i++) { |
| 353 | if (p_trackers[i]->actions.find(action) == -1) { |
| 354 | p_trackers[i]->actions.push_back(action); |
| 355 | } |
| 356 | } |
| 357 | |
| 358 | return action; |
| 359 | } |
| 360 | |
| 361 | OpenXRInterface::Action *OpenXRInterface::find_action(const String &p_action_name) { |
| 362 | // We just find the first action by this name |
| 363 | |
| 364 | for (int i = 0; i < action_sets.size(); i++) { |
| 365 | for (int j = 0; j < action_sets[i]->actions.size(); j++) { |
| 366 | if (action_sets[i]->actions[j]->action_name == p_action_name) { |
| 367 | return action_sets[i]->actions[j]; |
| 368 | } |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | // not found |
| 373 | return nullptr; |
| 374 | } |
| 375 | |
| 376 | void OpenXRInterface::free_actions(ActionSet *p_action_set) { |
| 377 | ERR_FAIL_NULL(openxr_api); |
| 378 | |
| 379 | for (int i = 0; i < p_action_set->actions.size(); i++) { |
| 380 | Action *action = p_action_set->actions[i]; |
| 381 | |
| 382 | openxr_api->action_free(action->action_rid); |
| 383 | |
| 384 | memdelete(action); |
| 385 | } |
| 386 | p_action_set->actions.clear(); |
| 387 | } |
| 388 | |
| 389 | OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_tracker_name, bool p_create) { |
| 390 | XRServer *xr_server = XRServer::get_singleton(); |
| 391 | ERR_FAIL_NULL_V(xr_server, nullptr); |
| 392 | ERR_FAIL_NULL_V(openxr_api, nullptr); |
| 393 | |
| 394 | Tracker *tracker = nullptr; |
| 395 | for (int i = 0; i < trackers.size(); i++) { |
| 396 | tracker = trackers[i]; |
| 397 | if (tracker->tracker_name == p_tracker_name) { |
| 398 | return tracker; |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | if (!p_create) { |
| 403 | return nullptr; |
| 404 | } |
| 405 | |
| 406 | ERR_FAIL_COND_V(!openxr_api->is_top_level_path_supported(p_tracker_name), nullptr); |
| 407 | |
| 408 | // Create our RID |
| 409 | RID tracker_rid = openxr_api->tracker_create(p_tracker_name); |
| 410 | ERR_FAIL_COND_V(tracker_rid.is_null(), nullptr); |
| 411 | |
| 412 | // create our positional tracker |
| 413 | Ref<XRPositionalTracker> positional_tracker; |
| 414 | positional_tracker.instantiate(); |
| 415 | |
| 416 | // We have standardized some names to make things nicer to the user so lets recognize the toplevel paths related to these. |
| 417 | if (p_tracker_name == "/user/hand/left" ) { |
| 418 | positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); |
| 419 | positional_tracker->set_tracker_name("left_hand" ); |
| 420 | positional_tracker->set_tracker_desc("Left hand controller" ); |
| 421 | positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT); |
| 422 | } else if (p_tracker_name == "/user/hand/right" ) { |
| 423 | positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); |
| 424 | positional_tracker->set_tracker_name("right_hand" ); |
| 425 | positional_tracker->set_tracker_desc("Right hand controller" ); |
| 426 | positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT); |
| 427 | } else { |
| 428 | positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); |
| 429 | positional_tracker->set_tracker_name(p_tracker_name); |
| 430 | positional_tracker->set_tracker_desc(p_tracker_name); |
| 431 | } |
| 432 | positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); |
| 433 | xr_server->add_tracker(positional_tracker); |
| 434 | |
| 435 | // create a new entry |
| 436 | tracker = memnew(Tracker); |
| 437 | tracker->tracker_name = p_tracker_name; |
| 438 | tracker->tracker_rid = tracker_rid; |
| 439 | tracker->positional_tracker = positional_tracker; |
| 440 | tracker->interaction_profile = RID(); |
| 441 | trackers.push_back(tracker); |
| 442 | |
| 443 | return tracker; |
| 444 | } |
| 445 | |
| 446 | void OpenXRInterface::tracker_profile_changed(RID p_tracker, RID p_interaction_profile) { |
| 447 | Tracker *tracker = nullptr; |
| 448 | for (int i = 0; i < trackers.size() && tracker == nullptr; i++) { |
| 449 | if (trackers[i]->tracker_rid == p_tracker) { |
| 450 | tracker = trackers[i]; |
| 451 | } |
| 452 | } |
| 453 | ERR_FAIL_NULL(tracker); |
| 454 | |
| 455 | tracker->interaction_profile = p_interaction_profile; |
| 456 | |
| 457 | if (p_interaction_profile.is_null()) { |
| 458 | print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + INTERACTION_PROFILE_NONE); |
| 459 | tracker->positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); |
| 460 | } else { |
| 461 | String name = openxr_api->interaction_profile_get_name(p_interaction_profile); |
| 462 | print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + name); |
| 463 | tracker->positional_tracker->set_tracker_profile(name); |
| 464 | } |
| 465 | } |
| 466 | |
| 467 | void OpenXRInterface::handle_tracker(Tracker *p_tracker) { |
| 468 | ERR_FAIL_NULL(openxr_api); |
| 469 | ERR_FAIL_COND(p_tracker->positional_tracker.is_null()); |
| 470 | |
| 471 | // Note, which actions are actually bound to inputs are handled by our interaction profiles however interaction |
| 472 | // profiles are suggested bindings for controller types we know about. OpenXR runtimes can stray away from these |
| 473 | // and rebind them or even offer bindings to controllers that are not known to us. |
| 474 | |
| 475 | // We don't really have a consistent way to detect whether a controller is active however as long as it is |
| 476 | // unbound it seems to be unavailable, so far unknown controller seem to mimic one of the profiles we've |
| 477 | // supplied. |
| 478 | if (p_tracker->interaction_profile.is_null()) { |
| 479 | return; |
| 480 | } |
| 481 | |
| 482 | // We check all actions that are related to our tracker. |
| 483 | for (int i = 0; i < p_tracker->actions.size(); i++) { |
| 484 | Action *action = p_tracker->actions[i]; |
| 485 | switch (action->action_type) { |
| 486 | case OpenXRAction::OPENXR_ACTION_BOOL: { |
| 487 | bool pressed = openxr_api->get_action_bool(action->action_rid, p_tracker->tracker_rid); |
| 488 | p_tracker->positional_tracker->set_input(action->action_name, Variant(pressed)); |
| 489 | } break; |
| 490 | case OpenXRAction::OPENXR_ACTION_FLOAT: { |
| 491 | real_t value = openxr_api->get_action_float(action->action_rid, p_tracker->tracker_rid); |
| 492 | p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); |
| 493 | } break; |
| 494 | case OpenXRAction::OPENXR_ACTION_VECTOR2: { |
| 495 | Vector2 value = openxr_api->get_action_vector2(action->action_rid, p_tracker->tracker_rid); |
| 496 | p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); |
| 497 | } break; |
| 498 | case OpenXRAction::OPENXR_ACTION_POSE: { |
| 499 | Transform3D transform; |
| 500 | Vector3 linear, angular; |
| 501 | |
| 502 | XRPose::TrackingConfidence confidence = openxr_api->get_action_pose(action->action_rid, p_tracker->tracker_rid, transform, linear, angular); |
| 503 | |
| 504 | if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { |
| 505 | p_tracker->positional_tracker->set_pose(action->action_name, transform, linear, angular, confidence); |
| 506 | } else { |
| 507 | p_tracker->positional_tracker->invalidate_pose(action->action_name); |
| 508 | } |
| 509 | } break; |
| 510 | default: { |
| 511 | // not yet supported |
| 512 | } break; |
| 513 | } |
| 514 | } |
| 515 | } |
| 516 | |
| 517 | void OpenXRInterface::trigger_haptic_pulse(const String &p_action_name, const StringName &p_tracker_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec) { |
| 518 | ERR_FAIL_NULL(openxr_api); |
| 519 | |
| 520 | Action *action = find_action(p_action_name); |
| 521 | ERR_FAIL_NULL(action); |
| 522 | |
| 523 | // We need to map our tracker name to our OpenXR name for our inbuild names. |
| 524 | String tracker_name = p_tracker_name; |
| 525 | if (tracker_name == "left_hand" ) { |
| 526 | tracker_name = "/user/hand/left" ; |
| 527 | } else if (tracker_name == "right_hand" ) { |
| 528 | tracker_name = "/user/hand/right" ; |
| 529 | } |
| 530 | Tracker *tracker = find_tracker(tracker_name); |
| 531 | ERR_FAIL_NULL(tracker); |
| 532 | |
| 533 | // TODO OpenXR does not support delay, so we may need to add support for that somehow... |
| 534 | |
| 535 | XrDuration duration = XrDuration(p_duration_sec * 1000000000.0); // seconds -> nanoseconds |
| 536 | |
| 537 | openxr_api->trigger_haptic_pulse(action->action_rid, tracker->tracker_rid, p_frequency, p_amplitude, duration); |
| 538 | } |
| 539 | |
| 540 | void OpenXRInterface::free_trackers() { |
| 541 | XRServer *xr_server = XRServer::get_singleton(); |
| 542 | ERR_FAIL_NULL(xr_server); |
| 543 | ERR_FAIL_NULL(openxr_api); |
| 544 | |
| 545 | for (int i = 0; i < trackers.size(); i++) { |
| 546 | Tracker *tracker = trackers[i]; |
| 547 | |
| 548 | openxr_api->tracker_free(tracker->tracker_rid); |
| 549 | xr_server->remove_tracker(tracker->positional_tracker); |
| 550 | tracker->positional_tracker.unref(); |
| 551 | |
| 552 | memdelete(tracker); |
| 553 | } |
| 554 | trackers.clear(); |
| 555 | } |
| 556 | |
| 557 | void OpenXRInterface::free_interaction_profiles() { |
| 558 | ERR_FAIL_NULL(openxr_api); |
| 559 | |
| 560 | for (int i = 0; i < interaction_profiles.size(); i++) { |
| 561 | openxr_api->interaction_profile_free(interaction_profiles[i]); |
| 562 | } |
| 563 | interaction_profiles.clear(); |
| 564 | } |
| 565 | |
| 566 | bool OpenXRInterface::initialize_on_startup() const { |
| 567 | if (openxr_api == nullptr) { |
| 568 | return false; |
| 569 | } else if (!openxr_api->is_initialized()) { |
| 570 | return false; |
| 571 | } else { |
| 572 | return true; |
| 573 | } |
| 574 | } |
| 575 | |
| 576 | bool OpenXRInterface::is_initialized() const { |
| 577 | return initialized; |
| 578 | }; |
| 579 | |
| 580 | bool OpenXRInterface::initialize() { |
| 581 | XRServer *xr_server = XRServer::get_singleton(); |
| 582 | ERR_FAIL_NULL_V(xr_server, false); |
| 583 | |
| 584 | if (openxr_api == nullptr) { |
| 585 | return false; |
| 586 | } else if (!openxr_api->is_initialized()) { |
| 587 | return false; |
| 588 | } else if (initialized) { |
| 589 | return true; |
| 590 | } |
| 591 | |
| 592 | // load up our action sets before setting up our session, note that our profiles are suggestions, OpenXR takes ownership of (re)binding |
| 593 | _load_action_map(); |
| 594 | |
| 595 | if (!openxr_api->initialize_session()) { |
| 596 | return false; |
| 597 | } |
| 598 | |
| 599 | // we must create a tracker for our head |
| 600 | head.instantiate(); |
| 601 | head->set_tracker_type(XRServer::TRACKER_HEAD); |
| 602 | head->set_tracker_name("head" ); |
| 603 | head->set_tracker_desc("Players head" ); |
| 604 | xr_server->add_tracker(head); |
| 605 | |
| 606 | // attach action sets |
| 607 | Vector<RID> loaded_action_sets; |
| 608 | for (int i = 0; i < action_sets.size(); i++) { |
| 609 | loaded_action_sets.append(action_sets[i]->action_set_rid); |
| 610 | } |
| 611 | openxr_api->attach_action_sets(loaded_action_sets); |
| 612 | |
| 613 | // make this our primary interface |
| 614 | xr_server->set_primary_interface(this); |
| 615 | |
| 616 | initialized = true; |
| 617 | |
| 618 | return initialized; |
| 619 | } |
| 620 | |
| 621 | void OpenXRInterface::uninitialize() { |
| 622 | // Our OpenXR driver will clean itself up properly when Godot exits, so we just do some basic stuff here |
| 623 | |
| 624 | // end the session if we need to? |
| 625 | |
| 626 | // cleanup stuff |
| 627 | free_trackers(); |
| 628 | free_interaction_profiles(); |
| 629 | free_action_sets(); |
| 630 | |
| 631 | XRServer *xr_server = XRServer::get_singleton(); |
| 632 | if (xr_server) { |
| 633 | if (head.is_valid()) { |
| 634 | xr_server->remove_tracker(head); |
| 635 | head.unref(); |
| 636 | } |
| 637 | } |
| 638 | |
| 639 | initialized = false; |
| 640 | } |
| 641 | |
| 642 | Dictionary OpenXRInterface::get_system_info() { |
| 643 | Dictionary dict; |
| 644 | |
| 645 | if (openxr_api) { |
| 646 | dict[SNAME("XRRuntimeName" )] = openxr_api->get_runtime_name(); |
| 647 | dict[SNAME("XRRuntimeVersion" )] = openxr_api->get_runtime_version(); |
| 648 | } |
| 649 | |
| 650 | return dict; |
| 651 | } |
| 652 | |
| 653 | bool OpenXRInterface::supports_play_area_mode(XRInterface::PlayAreaMode p_mode) { |
| 654 | return false; |
| 655 | } |
| 656 | |
| 657 | XRInterface::PlayAreaMode OpenXRInterface::get_play_area_mode() const { |
| 658 | return XRInterface::XR_PLAY_AREA_UNKNOWN; |
| 659 | } |
| 660 | |
| 661 | bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) { |
| 662 | return false; |
| 663 | } |
| 664 | |
| 665 | float OpenXRInterface::get_display_refresh_rate() const { |
| 666 | if (openxr_api == nullptr) { |
| 667 | return 0.0; |
| 668 | } else if (!openxr_api->is_initialized()) { |
| 669 | return 0.0; |
| 670 | } else { |
| 671 | return openxr_api->get_display_refresh_rate(); |
| 672 | } |
| 673 | } |
| 674 | |
| 675 | void OpenXRInterface::set_display_refresh_rate(float p_refresh_rate) { |
| 676 | if (openxr_api == nullptr) { |
| 677 | return; |
| 678 | } else if (!openxr_api->is_initialized()) { |
| 679 | return; |
| 680 | } else { |
| 681 | openxr_api->set_display_refresh_rate(p_refresh_rate); |
| 682 | } |
| 683 | } |
| 684 | |
| 685 | Array OpenXRInterface::get_available_display_refresh_rates() const { |
| 686 | if (openxr_api == nullptr) { |
| 687 | return Array(); |
| 688 | } else if (!openxr_api->is_initialized()) { |
| 689 | return Array(); |
| 690 | } else { |
| 691 | return openxr_api->get_available_display_refresh_rates(); |
| 692 | } |
| 693 | } |
| 694 | |
| 695 | bool OpenXRInterface::is_action_set_active(const String &p_action_set) const { |
| 696 | for (ActionSet *action_set : action_sets) { |
| 697 | if (action_set->action_set_name == p_action_set) { |
| 698 | return action_set->is_active; |
| 699 | } |
| 700 | } |
| 701 | |
| 702 | WARN_PRINT("OpenXR: Unknown action set " + p_action_set); |
| 703 | return false; |
| 704 | } |
| 705 | |
| 706 | void OpenXRInterface::set_action_set_active(const String &p_action_set, bool p_active) { |
| 707 | for (ActionSet *action_set : action_sets) { |
| 708 | if (action_set->action_set_name == p_action_set) { |
| 709 | action_set->is_active = p_active; |
| 710 | return; |
| 711 | } |
| 712 | } |
| 713 | |
| 714 | WARN_PRINT("OpenXR: Unknown action set " + p_action_set); |
| 715 | } |
| 716 | |
| 717 | Array OpenXRInterface::get_action_sets() const { |
| 718 | Array arr; |
| 719 | |
| 720 | for (ActionSet *action_set : action_sets) { |
| 721 | arr.push_back(action_set->action_set_name); |
| 722 | } |
| 723 | |
| 724 | return arr; |
| 725 | } |
| 726 | |
| 727 | double OpenXRInterface::get_render_target_size_multiplier() const { |
| 728 | if (openxr_api == nullptr) { |
| 729 | return 1.0; |
| 730 | } else { |
| 731 | return openxr_api->get_render_target_size_multiplier(); |
| 732 | } |
| 733 | } |
| 734 | |
| 735 | void OpenXRInterface::set_render_target_size_multiplier(double multiplier) { |
| 736 | if (openxr_api == nullptr) { |
| 737 | return; |
| 738 | } else { |
| 739 | openxr_api->set_render_target_size_multiplier(multiplier); |
| 740 | } |
| 741 | } |
| 742 | |
| 743 | Size2 OpenXRInterface::get_render_target_size() { |
| 744 | if (openxr_api == nullptr) { |
| 745 | return Size2(); |
| 746 | } else { |
| 747 | return openxr_api->get_recommended_target_size(); |
| 748 | } |
| 749 | } |
| 750 | |
| 751 | uint32_t OpenXRInterface::get_view_count() { |
| 752 | // TODO set this based on our configuration |
| 753 | return 2; |
| 754 | } |
| 755 | |
| 756 | void OpenXRInterface::_set_default_pos(Transform3D &p_transform, double p_world_scale, uint64_t p_eye) { |
| 757 | p_transform = Transform3D(); |
| 758 | |
| 759 | // if we're not tracking, don't put our head on the floor... |
| 760 | p_transform.origin.y = 1.5 * p_world_scale; |
| 761 | |
| 762 | // overkill but.. |
| 763 | if (p_eye == 1) { |
| 764 | p_transform.origin.x = 0.03 * p_world_scale; |
| 765 | } else if (p_eye == 2) { |
| 766 | p_transform.origin.x = -0.03 * p_world_scale; |
| 767 | } |
| 768 | } |
| 769 | |
| 770 | Transform3D OpenXRInterface::get_camera_transform() { |
| 771 | XRServer *xr_server = XRServer::get_singleton(); |
| 772 | ERR_FAIL_NULL_V(xr_server, Transform3D()); |
| 773 | |
| 774 | Transform3D hmd_transform; |
| 775 | double world_scale = xr_server->get_world_scale(); |
| 776 | |
| 777 | // head_transform should be updated in process |
| 778 | |
| 779 | hmd_transform.basis = head_transform.basis; |
| 780 | hmd_transform.origin = head_transform.origin * world_scale; |
| 781 | |
| 782 | return hmd_transform; |
| 783 | } |
| 784 | |
| 785 | Transform3D OpenXRInterface::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) { |
| 786 | XRServer *xr_server = XRServer::get_singleton(); |
| 787 | ERR_FAIL_NULL_V(xr_server, Transform3D()); |
| 788 | ERR_FAIL_UNSIGNED_INDEX_V_MSG(p_view, get_view_count(), Transform3D(), "View index outside bounds." ); |
| 789 | |
| 790 | Transform3D t; |
| 791 | if (openxr_api && openxr_api->get_view_transform(p_view, t)) { |
| 792 | // update our cached value if we have a valid transform |
| 793 | transform_for_view[p_view] = t; |
| 794 | } else { |
| 795 | // reuse cached value |
| 796 | t = transform_for_view[p_view]; |
| 797 | } |
| 798 | |
| 799 | // Apply our world scale |
| 800 | double world_scale = xr_server->get_world_scale(); |
| 801 | t.origin *= world_scale; |
| 802 | |
| 803 | return p_cam_transform * xr_server->get_reference_frame() * t; |
| 804 | } |
| 805 | |
| 806 | Projection OpenXRInterface::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) { |
| 807 | Projection cm; |
| 808 | ERR_FAIL_UNSIGNED_INDEX_V_MSG(p_view, get_view_count(), cm, "View index outside bounds." ); |
| 809 | |
| 810 | if (openxr_api) { |
| 811 | if (openxr_api->get_view_projection(p_view, p_z_near, p_z_far, cm)) { |
| 812 | return cm; |
| 813 | } |
| 814 | } |
| 815 | |
| 816 | // Failed to get from our OpenXR device? Default to some sort of sensible camera matrix.. |
| 817 | cm.set_for_hmd(p_view + 1, 1.0, 6.0, 14.5, 4.0, 1.5, p_z_near, p_z_far); |
| 818 | |
| 819 | return cm; |
| 820 | } |
| 821 | |
| 822 | RID OpenXRInterface::get_color_texture() { |
| 823 | if (openxr_api) { |
| 824 | return openxr_api->get_color_texture(); |
| 825 | } else { |
| 826 | return RID(); |
| 827 | } |
| 828 | } |
| 829 | |
| 830 | RID OpenXRInterface::get_depth_texture() { |
| 831 | if (openxr_api) { |
| 832 | return openxr_api->get_depth_texture(); |
| 833 | } else { |
| 834 | return RID(); |
| 835 | } |
| 836 | } |
| 837 | |
| 838 | void OpenXRInterface::process() { |
| 839 | if (openxr_api) { |
| 840 | // do our normal process |
| 841 | if (openxr_api->process()) { |
| 842 | Transform3D t; |
| 843 | Vector3 linear_velocity; |
| 844 | Vector3 angular_velocity; |
| 845 | XRPose::TrackingConfidence confidence = openxr_api->get_head_center(t, linear_velocity, angular_velocity); |
| 846 | if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { |
| 847 | // Only update our transform if we have one to update it with |
| 848 | // note that poses are stored without world scale and reference frame applied! |
| 849 | head_transform = t; |
| 850 | head_linear_velocity = linear_velocity; |
| 851 | head_angular_velocity = angular_velocity; |
| 852 | } |
| 853 | } |
| 854 | |
| 855 | // handle our action sets.... |
| 856 | Vector<RID> active_sets; |
| 857 | for (int i = 0; i < action_sets.size(); i++) { |
| 858 | if (action_sets[i]->is_active) { |
| 859 | active_sets.push_back(action_sets[i]->action_set_rid); |
| 860 | } |
| 861 | } |
| 862 | |
| 863 | if (openxr_api->sync_action_sets(active_sets)) { |
| 864 | for (int i = 0; i < trackers.size(); i++) { |
| 865 | handle_tracker(trackers[i]); |
| 866 | } |
| 867 | } |
| 868 | } |
| 869 | |
| 870 | if (head.is_valid()) { |
| 871 | // TODO figure out how to get our velocities |
| 872 | |
| 873 | head->set_pose("default" , head_transform, head_linear_velocity, head_angular_velocity); |
| 874 | |
| 875 | // TODO set confidence on pose once we support tracking this.. |
| 876 | } |
| 877 | } |
| 878 | |
| 879 | void OpenXRInterface::pre_render() { |
| 880 | if (openxr_api) { |
| 881 | openxr_api->pre_render(); |
| 882 | } |
| 883 | } |
| 884 | |
| 885 | bool OpenXRInterface::pre_draw_viewport(RID p_render_target) { |
| 886 | if (openxr_api) { |
| 887 | return openxr_api->pre_draw_viewport(p_render_target); |
| 888 | } else { |
| 889 | // don't render |
| 890 | return false; |
| 891 | } |
| 892 | } |
| 893 | |
| 894 | Vector<BlitToScreen> OpenXRInterface::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) { |
| 895 | Vector<BlitToScreen> blit_to_screen; |
| 896 | |
| 897 | #ifndef ANDROID_ENABLED |
| 898 | // If separate HMD we should output one eye to screen |
| 899 | if (p_screen_rect != Rect2()) { |
| 900 | BlitToScreen blit; |
| 901 | |
| 902 | blit.render_target = p_render_target; |
| 903 | blit.multi_view.use_layer = true; |
| 904 | blit.multi_view.layer = 0; |
| 905 | blit.lens_distortion.apply = false; |
| 906 | |
| 907 | Size2 render_size = get_render_target_size(); |
| 908 | Rect2 dst_rect = p_screen_rect; |
| 909 | float new_height = dst_rect.size.x * (render_size.y / render_size.x); |
| 910 | if (new_height > dst_rect.size.y) { |
| 911 | dst_rect.position.y = (0.5 * dst_rect.size.y) - (0.5 * new_height); |
| 912 | dst_rect.size.y = new_height; |
| 913 | } else { |
| 914 | float new_width = dst_rect.size.y * (render_size.x / render_size.y); |
| 915 | |
| 916 | dst_rect.position.x = (0.5 * dst_rect.size.x) - (0.5 * new_width); |
| 917 | dst_rect.size.x = new_width; |
| 918 | } |
| 919 | |
| 920 | blit.dst_rect = dst_rect; |
| 921 | blit_to_screen.push_back(blit); |
| 922 | } |
| 923 | #endif |
| 924 | |
| 925 | if (openxr_api) { |
| 926 | openxr_api->post_draw_viewport(p_render_target); |
| 927 | } |
| 928 | |
| 929 | return blit_to_screen; |
| 930 | } |
| 931 | |
| 932 | void OpenXRInterface::end_frame() { |
| 933 | if (openxr_api) { |
| 934 | openxr_api->end_frame(); |
| 935 | } |
| 936 | } |
| 937 | |
| 938 | bool OpenXRInterface::is_passthrough_supported() { |
| 939 | return passthrough_wrapper != nullptr && passthrough_wrapper->is_passthrough_supported(); |
| 940 | } |
| 941 | |
| 942 | bool OpenXRInterface::is_passthrough_enabled() { |
| 943 | return passthrough_wrapper != nullptr && passthrough_wrapper->is_passthrough_enabled(); |
| 944 | } |
| 945 | |
| 946 | bool OpenXRInterface::start_passthrough() { |
| 947 | return passthrough_wrapper != nullptr && passthrough_wrapper->start_passthrough(); |
| 948 | } |
| 949 | |
| 950 | void OpenXRInterface::stop_passthrough() { |
| 951 | if (passthrough_wrapper) { |
| 952 | passthrough_wrapper->stop_passthrough(); |
| 953 | } |
| 954 | } |
| 955 | |
| 956 | Array OpenXRInterface::get_supported_environment_blend_modes() { |
| 957 | Array modes; |
| 958 | |
| 959 | if (!openxr_api) { |
| 960 | return modes; |
| 961 | } |
| 962 | |
| 963 | uint32_t count = 0; |
| 964 | const XrEnvironmentBlendMode *env_blend_modes = openxr_api->get_supported_environment_blend_modes(count); |
| 965 | |
| 966 | if (!env_blend_modes) { |
| 967 | return modes; |
| 968 | } |
| 969 | |
| 970 | for (uint32_t i = 0; i < count; i++) { |
| 971 | switch (env_blend_modes[i]) { |
| 972 | case XR_ENVIRONMENT_BLEND_MODE_OPAQUE: |
| 973 | modes.push_back(XR_ENV_BLEND_MODE_OPAQUE); |
| 974 | break; |
| 975 | case XR_ENVIRONMENT_BLEND_MODE_ADDITIVE: |
| 976 | modes.push_back(XR_ENV_BLEND_MODE_ADDITIVE); |
| 977 | break; |
| 978 | case XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND: |
| 979 | modes.push_back(XR_ENV_BLEND_MODE_ALPHA_BLEND); |
| 980 | break; |
| 981 | default: |
| 982 | WARN_PRINT("Unsupported blend mode found: " + String::num_int64(int64_t(env_blend_modes[i]))); |
| 983 | } |
| 984 | } |
| 985 | return modes; |
| 986 | } |
| 987 | |
| 988 | XRInterface::EnvironmentBlendMode OpenXRInterface::get_environment_blend_mode() const { |
| 989 | if (openxr_api) { |
| 990 | XrEnvironmentBlendMode oxr_blend_mode = openxr_api->get_environment_blend_mode(); |
| 991 | switch (oxr_blend_mode) { |
| 992 | case XR_ENVIRONMENT_BLEND_MODE_OPAQUE: { |
| 993 | return XR_ENV_BLEND_MODE_OPAQUE; |
| 994 | } break; |
| 995 | case XR_ENVIRONMENT_BLEND_MODE_ADDITIVE: { |
| 996 | return XR_ENV_BLEND_MODE_ADDITIVE; |
| 997 | } break; |
| 998 | case XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND: { |
| 999 | return XR_ENV_BLEND_MODE_ALPHA_BLEND; |
| 1000 | } break; |
| 1001 | default: |
| 1002 | break; |
| 1003 | } |
| 1004 | } |
| 1005 | |
| 1006 | return XR_ENV_BLEND_MODE_OPAQUE; |
| 1007 | } |
| 1008 | |
| 1009 | bool OpenXRInterface::set_environment_blend_mode(XRInterface::EnvironmentBlendMode mode) { |
| 1010 | if (openxr_api) { |
| 1011 | XrEnvironmentBlendMode oxr_blend_mode; |
| 1012 | switch (mode) { |
| 1013 | case XR_ENV_BLEND_MODE_OPAQUE: |
| 1014 | oxr_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; |
| 1015 | break; |
| 1016 | case XR_ENV_BLEND_MODE_ADDITIVE: |
| 1017 | oxr_blend_mode = XR_ENVIRONMENT_BLEND_MODE_ADDITIVE; |
| 1018 | break; |
| 1019 | case XR_ENV_BLEND_MODE_ALPHA_BLEND: |
| 1020 | oxr_blend_mode = XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND; |
| 1021 | break; |
| 1022 | default: |
| 1023 | WARN_PRINT("Unknown blend mode requested: " + String::num_int64(int64_t(mode))); |
| 1024 | oxr_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; |
| 1025 | } |
| 1026 | return openxr_api->set_environment_blend_mode(oxr_blend_mode); |
| 1027 | } |
| 1028 | return false; |
| 1029 | } |
| 1030 | |
| 1031 | void OpenXRInterface::on_state_ready() { |
| 1032 | emit_signal(SNAME("session_begun" )); |
| 1033 | } |
| 1034 | |
| 1035 | void OpenXRInterface::on_state_visible() { |
| 1036 | emit_signal(SNAME("session_visible" )); |
| 1037 | } |
| 1038 | |
| 1039 | void OpenXRInterface::on_state_focused() { |
| 1040 | emit_signal(SNAME("session_focussed" )); |
| 1041 | } |
| 1042 | |
| 1043 | void OpenXRInterface::on_state_stopping() { |
| 1044 | emit_signal(SNAME("session_stopping" )); |
| 1045 | } |
| 1046 | |
| 1047 | void OpenXRInterface::on_pose_recentered() { |
| 1048 | emit_signal(SNAME("pose_recentered" )); |
| 1049 | } |
| 1050 | |
| 1051 | /** Hand tracking. */ |
| 1052 | void OpenXRInterface::set_motion_range(const Hand p_hand, const HandMotionRange p_motion_range) { |
| 1053 | ERR_FAIL_INDEX(p_hand, HAND_MAX); |
| 1054 | ERR_FAIL_INDEX(p_motion_range, HAND_MOTION_RANGE_MAX); |
| 1055 | |
| 1056 | OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); |
| 1057 | if (hand_tracking_ext && hand_tracking_ext->get_active()) { |
| 1058 | XrHandJointsMotionRangeEXT xr_motion_range; |
| 1059 | switch (p_motion_range) { |
| 1060 | case HAND_MOTION_RANGE_UNOBSTRUCTED: |
| 1061 | xr_motion_range = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT; |
| 1062 | break; |
| 1063 | case HAND_MOTION_RANGE_CONFORM_TO_CONTROLLER: |
| 1064 | xr_motion_range = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT; |
| 1065 | break; |
| 1066 | default: |
| 1067 | // Shouldn't get here, ERR_FAIL_INDEX should have caught this... |
| 1068 | xr_motion_range = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT; |
| 1069 | break; |
| 1070 | } |
| 1071 | |
| 1072 | hand_tracking_ext->set_motion_range(uint32_t(p_hand), xr_motion_range); |
| 1073 | } |
| 1074 | } |
| 1075 | |
| 1076 | OpenXRInterface::HandMotionRange OpenXRInterface::get_motion_range(const Hand p_hand) const { |
| 1077 | ERR_FAIL_INDEX_V(p_hand, HAND_MAX, HAND_MOTION_RANGE_MAX); |
| 1078 | |
| 1079 | OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); |
| 1080 | if (hand_tracking_ext && hand_tracking_ext->get_active()) { |
| 1081 | XrHandJointsMotionRangeEXT xr_motion_range = hand_tracking_ext->get_motion_range(uint32_t(p_hand)); |
| 1082 | |
| 1083 | switch (xr_motion_range) { |
| 1084 | case XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT: |
| 1085 | return HAND_MOTION_RANGE_UNOBSTRUCTED; |
| 1086 | case XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT: |
| 1087 | return HAND_MOTION_RANGE_CONFORM_TO_CONTROLLER; |
| 1088 | default: |
| 1089 | ERR_FAIL_V_MSG(HAND_MOTION_RANGE_MAX, "Unknown motion range returned by OpenXR" ); |
| 1090 | } |
| 1091 | } |
| 1092 | |
| 1093 | return HAND_MOTION_RANGE_MAX; |
| 1094 | } |
| 1095 | |
| 1096 | Quaternion OpenXRInterface::get_hand_joint_rotation(Hand p_hand, HandJoints p_joint) const { |
| 1097 | OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); |
| 1098 | if (hand_tracking_ext && hand_tracking_ext->get_active()) { |
| 1099 | return hand_tracking_ext->get_hand_joint_rotation(uint32_t(p_hand), XrHandJointEXT(p_joint)); |
| 1100 | } |
| 1101 | |
| 1102 | return Quaternion(); |
| 1103 | } |
| 1104 | |
| 1105 | Vector3 OpenXRInterface::get_hand_joint_position(Hand p_hand, HandJoints p_joint) const { |
| 1106 | OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); |
| 1107 | if (hand_tracking_ext && hand_tracking_ext->get_active()) { |
| 1108 | return hand_tracking_ext->get_hand_joint_position(uint32_t(p_hand), XrHandJointEXT(p_joint)); |
| 1109 | } |
| 1110 | |
| 1111 | return Vector3(); |
| 1112 | } |
| 1113 | |
| 1114 | float OpenXRInterface::get_hand_joint_radius(Hand p_hand, HandJoints p_joint) const { |
| 1115 | OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); |
| 1116 | if (hand_tracking_ext && hand_tracking_ext->get_active()) { |
| 1117 | return hand_tracking_ext->get_hand_joint_radius(uint32_t(p_hand), XrHandJointEXT(p_joint)); |
| 1118 | } |
| 1119 | |
| 1120 | return 0.0; |
| 1121 | } |
| 1122 | |
| 1123 | Vector3 OpenXRInterface::get_hand_joint_linear_velocity(Hand p_hand, HandJoints p_joint) const { |
| 1124 | OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); |
| 1125 | if (hand_tracking_ext && hand_tracking_ext->get_active()) { |
| 1126 | return hand_tracking_ext->get_hand_joint_linear_velocity(uint32_t(p_hand), XrHandJointEXT(p_joint)); |
| 1127 | } |
| 1128 | |
| 1129 | return Vector3(); |
| 1130 | } |
| 1131 | |
| 1132 | Vector3 OpenXRInterface::get_hand_joint_angular_velocity(Hand p_hand, HandJoints p_joint) const { |
| 1133 | OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); |
| 1134 | if (hand_tracking_ext && hand_tracking_ext->get_active()) { |
| 1135 | return hand_tracking_ext->get_hand_joint_angular_velocity(uint32_t(p_hand), XrHandJointEXT(p_joint)); |
| 1136 | } |
| 1137 | |
| 1138 | return Vector3(); |
| 1139 | } |
| 1140 | |
| 1141 | OpenXRInterface::OpenXRInterface() { |
| 1142 | openxr_api = OpenXRAPI::get_singleton(); |
| 1143 | if (openxr_api) { |
| 1144 | openxr_api->set_xr_interface(this); |
| 1145 | } |
| 1146 | |
| 1147 | // while we don't have head tracking, don't put the headset on the floor... |
| 1148 | _set_default_pos(head_transform, 1.0, 0); |
| 1149 | _set_default_pos(transform_for_view[0], 1.0, 1); |
| 1150 | _set_default_pos(transform_for_view[1], 1.0, 2); |
| 1151 | |
| 1152 | passthrough_wrapper = OpenXRFbPassthroughExtensionWrapper::get_singleton(); |
| 1153 | } |
| 1154 | |
| 1155 | OpenXRInterface::~OpenXRInterface() { |
| 1156 | if (is_initialized()) { |
| 1157 | uninitialize(); |
| 1158 | } |
| 1159 | |
| 1160 | if (openxr_api) { |
| 1161 | openxr_api->set_xr_interface(nullptr); |
| 1162 | openxr_api = nullptr; |
| 1163 | } |
| 1164 | } |
| 1165 | |