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
39void 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
111StringName OpenXRInterface::get_name() const {
112 return StringName("OpenXR");
113};
114
115uint32_t OpenXRInterface::get_capabilities() const {
116 return XRInterface::XR_VR + XRInterface::XR_STEREO;
117};
118
119PackedStringArray 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
148XRInterface::TrackingStatus OpenXRInterface::get_tracking_status() const {
149 return tracking_state;
150}
151
152void 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
279OpenXRInterface::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
299void 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
314OpenXRInterface::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
361OpenXRInterface::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
376void 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
389OpenXRInterface::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
446void 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
467void 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
517void 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
540void 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
557void 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
566bool 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
576bool OpenXRInterface::is_initialized() const {
577 return initialized;
578};
579
580bool 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
621void 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
642Dictionary 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
653bool OpenXRInterface::supports_play_area_mode(XRInterface::PlayAreaMode p_mode) {
654 return false;
655}
656
657XRInterface::PlayAreaMode OpenXRInterface::get_play_area_mode() const {
658 return XRInterface::XR_PLAY_AREA_UNKNOWN;
659}
660
661bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) {
662 return false;
663}
664
665float 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
675void 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
685Array 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
695bool 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
706void 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
717Array 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
727double 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
735void 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
743Size2 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
751uint32_t OpenXRInterface::get_view_count() {
752 // TODO set this based on our configuration
753 return 2;
754}
755
756void 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
770Transform3D 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
785Transform3D 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
806Projection 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
822RID OpenXRInterface::get_color_texture() {
823 if (openxr_api) {
824 return openxr_api->get_color_texture();
825 } else {
826 return RID();
827 }
828}
829
830RID OpenXRInterface::get_depth_texture() {
831 if (openxr_api) {
832 return openxr_api->get_depth_texture();
833 } else {
834 return RID();
835 }
836}
837
838void 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
879void OpenXRInterface::pre_render() {
880 if (openxr_api) {
881 openxr_api->pre_render();
882 }
883}
884
885bool 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
894Vector<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
932void OpenXRInterface::end_frame() {
933 if (openxr_api) {
934 openxr_api->end_frame();
935 }
936}
937
938bool OpenXRInterface::is_passthrough_supported() {
939 return passthrough_wrapper != nullptr && passthrough_wrapper->is_passthrough_supported();
940}
941
942bool OpenXRInterface::is_passthrough_enabled() {
943 return passthrough_wrapper != nullptr && passthrough_wrapper->is_passthrough_enabled();
944}
945
946bool OpenXRInterface::start_passthrough() {
947 return passthrough_wrapper != nullptr && passthrough_wrapper->start_passthrough();
948}
949
950void OpenXRInterface::stop_passthrough() {
951 if (passthrough_wrapper) {
952 passthrough_wrapper->stop_passthrough();
953 }
954}
955
956Array 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
988XRInterface::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
1009bool 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
1031void OpenXRInterface::on_state_ready() {
1032 emit_signal(SNAME("session_begun"));
1033}
1034
1035void OpenXRInterface::on_state_visible() {
1036 emit_signal(SNAME("session_visible"));
1037}
1038
1039void OpenXRInterface::on_state_focused() {
1040 emit_signal(SNAME("session_focussed"));
1041}
1042
1043void OpenXRInterface::on_state_stopping() {
1044 emit_signal(SNAME("session_stopping"));
1045}
1046
1047void OpenXRInterface::on_pose_recentered() {
1048 emit_signal(SNAME("pose_recentered"));
1049}
1050
1051/** Hand tracking. */
1052void 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
1076OpenXRInterface::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
1096Quaternion 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
1105Vector3 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
1114float 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
1123Vector3 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
1132Vector3 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
1141OpenXRInterface::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
1155OpenXRInterface::~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