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 | |