1/**************************************************************************/
2/* xr_server.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 "xr_server.h"
32#include "core/config/project_settings.h"
33#include "xr/xr_interface.h"
34#include "xr/xr_positional_tracker.h"
35
36XRServer::XRMode XRServer::xr_mode = XRMODE_DEFAULT;
37
38XRServer::XRMode XRServer::get_xr_mode() {
39 return xr_mode;
40}
41
42void XRServer::set_xr_mode(XRServer::XRMode p_mode) {
43 xr_mode = p_mode;
44}
45
46XRServer *XRServer::singleton = nullptr;
47
48XRServer *XRServer::get_singleton() {
49 return singleton;
50};
51
52void XRServer::_bind_methods() {
53 ClassDB::bind_method(D_METHOD("get_world_scale"), &XRServer::get_world_scale);
54 ClassDB::bind_method(D_METHOD("set_world_scale", "scale"), &XRServer::set_world_scale);
55 ClassDB::bind_method(D_METHOD("get_world_origin"), &XRServer::get_world_origin);
56 ClassDB::bind_method(D_METHOD("set_world_origin", "world_origin"), &XRServer::set_world_origin);
57 ClassDB::bind_method(D_METHOD("get_reference_frame"), &XRServer::get_reference_frame);
58 ClassDB::bind_method(D_METHOD("center_on_hmd", "rotation_mode", "keep_height"), &XRServer::center_on_hmd);
59 ClassDB::bind_method(D_METHOD("get_hmd_transform"), &XRServer::get_hmd_transform);
60
61 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "world_scale"), "set_world_scale", "get_world_scale");
62 ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "world_origin"), "set_world_origin", "get_world_origin");
63
64 ClassDB::bind_method(D_METHOD("add_interface", "interface"), &XRServer::add_interface);
65 ClassDB::bind_method(D_METHOD("get_interface_count"), &XRServer::get_interface_count);
66 ClassDB::bind_method(D_METHOD("remove_interface", "interface"), &XRServer::remove_interface);
67 ClassDB::bind_method(D_METHOD("get_interface", "idx"), &XRServer::get_interface);
68 ClassDB::bind_method(D_METHOD("get_interfaces"), &XRServer::get_interfaces);
69 ClassDB::bind_method(D_METHOD("find_interface", "name"), &XRServer::find_interface);
70
71 ClassDB::bind_method(D_METHOD("add_tracker", "tracker"), &XRServer::add_tracker);
72 ClassDB::bind_method(D_METHOD("remove_tracker", "tracker"), &XRServer::remove_tracker);
73 ClassDB::bind_method(D_METHOD("get_trackers", "tracker_types"), &XRServer::get_trackers);
74 ClassDB::bind_method(D_METHOD("get_tracker", "tracker_name"), &XRServer::get_tracker);
75
76 ClassDB::bind_method(D_METHOD("get_primary_interface"), &XRServer::get_primary_interface);
77 ClassDB::bind_method(D_METHOD("set_primary_interface", "interface"), &XRServer::set_primary_interface);
78
79 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "primary_interface"), "set_primary_interface", "get_primary_interface");
80
81 BIND_ENUM_CONSTANT(TRACKER_HEAD);
82 BIND_ENUM_CONSTANT(TRACKER_CONTROLLER);
83 BIND_ENUM_CONSTANT(TRACKER_BASESTATION);
84 BIND_ENUM_CONSTANT(TRACKER_ANCHOR);
85 BIND_ENUM_CONSTANT(TRACKER_ANY_KNOWN);
86 BIND_ENUM_CONSTANT(TRACKER_UNKNOWN);
87 BIND_ENUM_CONSTANT(TRACKER_ANY);
88
89 BIND_ENUM_CONSTANT(RESET_FULL_ROTATION);
90 BIND_ENUM_CONSTANT(RESET_BUT_KEEP_TILT);
91 BIND_ENUM_CONSTANT(DONT_RESET_ROTATION);
92
93 ADD_SIGNAL(MethodInfo("interface_added", PropertyInfo(Variant::STRING_NAME, "interface_name")));
94 ADD_SIGNAL(MethodInfo("interface_removed", PropertyInfo(Variant::STRING_NAME, "interface_name")));
95
96 ADD_SIGNAL(MethodInfo("tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
97 ADD_SIGNAL(MethodInfo("tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
98 ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
99};
100
101double XRServer::get_world_scale() const {
102 return world_scale;
103};
104
105void XRServer::set_world_scale(double p_world_scale) {
106 if (p_world_scale < 0.01) {
107 p_world_scale = 0.01;
108 } else if (p_world_scale > 1000.0) {
109 p_world_scale = 1000.0;
110 }
111
112 world_scale = p_world_scale;
113};
114
115Transform3D XRServer::get_world_origin() const {
116 return world_origin;
117};
118
119void XRServer::set_world_origin(const Transform3D &p_world_origin) {
120 world_origin = p_world_origin;
121};
122
123Transform3D XRServer::get_reference_frame() const {
124 return reference_frame;
125};
126
127void XRServer::center_on_hmd(RotationMode p_rotation_mode, bool p_keep_height) {
128 if (primary_interface == nullptr) {
129 return;
130 }
131
132 if (primary_interface->get_play_area_mode() == XRInterface::XR_PLAY_AREA_STAGE) {
133 // center_on_hmd is not available in this mode
134 reference_frame = Transform3D();
135 return;
136 }
137
138 // clear our current reference frame or we'll end up double adjusting it
139 reference_frame = Transform3D();
140
141 // requesting our EYE_MONO transform should return our current HMD position
142 Transform3D new_reference_frame = primary_interface->get_camera_transform();
143
144 // remove our tilt
145 if (p_rotation_mode == 1) {
146 // take the Y out of our Z
147 new_reference_frame.basis.set_column(2, Vector3(new_reference_frame.basis.rows[0][2], 0.0, new_reference_frame.basis.rows[2][2]).normalized());
148
149 // Y is straight up
150 new_reference_frame.basis.set_column(1, Vector3(0.0, 1.0, 0.0));
151
152 // and X is our cross reference
153 new_reference_frame.basis.set_column(0, new_reference_frame.basis.get_column(1).cross(new_reference_frame.basis.get_column(2)).normalized());
154 } else if (p_rotation_mode == 2) {
155 // remove our rotation, we're only interesting in centering on position
156 new_reference_frame.basis = Basis();
157 };
158
159 // don't negate our height
160 if (p_keep_height) {
161 new_reference_frame.origin.y = 0.0;
162 };
163
164 reference_frame = new_reference_frame.inverse();
165};
166
167Transform3D XRServer::get_hmd_transform() {
168 Transform3D hmd_transform;
169 if (primary_interface != nullptr) {
170 hmd_transform = primary_interface->get_camera_transform();
171 };
172 return hmd_transform;
173};
174
175void XRServer::add_interface(const Ref<XRInterface> &p_interface) {
176 ERR_FAIL_COND(p_interface.is_null());
177
178 for (int i = 0; i < interfaces.size(); i++) {
179 if (interfaces[i] == p_interface) {
180 ERR_PRINT("Interface was already added");
181 return;
182 };
183 };
184
185 interfaces.push_back(p_interface);
186 emit_signal(SNAME("interface_added"), p_interface->get_name());
187};
188
189void XRServer::remove_interface(const Ref<XRInterface> &p_interface) {
190 ERR_FAIL_COND(p_interface.is_null());
191
192 int idx = -1;
193 for (int i = 0; i < interfaces.size(); i++) {
194 if (interfaces[i] == p_interface) {
195 idx = i;
196 break;
197 };
198 };
199
200 ERR_FAIL_COND_MSG(idx == -1, "Interface not found.");
201
202 print_verbose("XR: Removed interface" + p_interface->get_name());
203
204 emit_signal(SNAME("interface_removed"), p_interface->get_name());
205 interfaces.remove_at(idx);
206};
207
208int XRServer::get_interface_count() const {
209 return interfaces.size();
210};
211
212Ref<XRInterface> XRServer::get_interface(int p_index) const {
213 ERR_FAIL_INDEX_V(p_index, interfaces.size(), nullptr);
214
215 return interfaces[p_index];
216};
217
218Ref<XRInterface> XRServer::find_interface(const String &p_name) const {
219 for (int i = 0; i < interfaces.size(); i++) {
220 if (interfaces[i]->get_name() == p_name) {
221 return interfaces[i];
222 };
223 };
224 return Ref<XRInterface>();
225};
226
227TypedArray<Dictionary> XRServer::get_interfaces() const {
228 Array ret;
229
230 for (int i = 0; i < interfaces.size(); i++) {
231 Dictionary iface_info;
232
233 iface_info["id"] = i;
234 iface_info["name"] = interfaces[i]->get_name();
235
236 ret.push_back(iface_info);
237 };
238
239 return ret;
240};
241
242Ref<XRInterface> XRServer::get_primary_interface() const {
243 return primary_interface;
244};
245
246void XRServer::set_primary_interface(const Ref<XRInterface> &p_primary_interface) {
247 if (p_primary_interface.is_null()) {
248 print_verbose("XR: Clearing primary interface");
249 primary_interface.unref();
250 } else {
251 primary_interface = p_primary_interface;
252
253 print_verbose("XR: Primary interface set to: " + primary_interface->get_name());
254 }
255};
256
257void XRServer::add_tracker(Ref<XRPositionalTracker> p_tracker) {
258 ERR_FAIL_COND(p_tracker.is_null());
259
260 StringName tracker_name = p_tracker->get_tracker_name();
261 if (trackers.has(tracker_name)) {
262 if (trackers[tracker_name] != p_tracker) {
263 // We already have a tracker with this name, we're going to replace it
264 trackers[tracker_name] = p_tracker;
265 emit_signal(SNAME("tracker_updated"), tracker_name, p_tracker->get_tracker_type());
266 }
267 } else {
268 trackers[tracker_name] = p_tracker;
269 emit_signal(SNAME("tracker_added"), tracker_name, p_tracker->get_tracker_type());
270 }
271};
272
273void XRServer::remove_tracker(Ref<XRPositionalTracker> p_tracker) {
274 ERR_FAIL_COND(p_tracker.is_null());
275
276 StringName tracker_name = p_tracker->get_tracker_name();
277 if (trackers.has(tracker_name)) {
278 // we send the signal right before removing it
279 emit_signal(SNAME("tracker_removed"), p_tracker->get_tracker_name(), p_tracker->get_tracker_type());
280
281 // and remove it
282 trackers.erase(tracker_name);
283 }
284};
285
286Dictionary XRServer::get_trackers(int p_tracker_types) {
287 Dictionary res;
288
289 for (int i = 0; i < trackers.size(); i++) {
290 Ref<XRPositionalTracker> tracker = trackers.get_value_at_index(i);
291 if (tracker.is_valid() && (tracker->get_tracker_type() & p_tracker_types) != 0) {
292 res[tracker->get_tracker_name()] = tracker;
293 }
294 }
295
296 return res;
297}
298
299Ref<XRPositionalTracker> XRServer::get_tracker(const StringName &p_name) const {
300 if (trackers.has(p_name)) {
301 return trackers[p_name];
302 } else {
303 // tracker hasn't been registered yet, which is fine, no need to spam the error log...
304 return Ref<XRPositionalTracker>();
305 }
306};
307
308PackedStringArray XRServer::get_suggested_tracker_names() const {
309 PackedStringArray arr;
310
311 for (int i = 0; i < interfaces.size(); i++) {
312 Ref<XRInterface> interface = interfaces[i];
313 PackedStringArray interface_arr = interface->get_suggested_tracker_names();
314 for (int a = 0; a < interface_arr.size(); a++) {
315 if (!arr.has(interface_arr[a])) {
316 arr.push_back(interface_arr[a]);
317 }
318 }
319 }
320
321 if (arr.size() == 0) {
322 // no suggestions from our tracker? include our defaults
323 arr.push_back(String("head"));
324 arr.push_back(String("left_hand"));
325 arr.push_back(String("right_hand"));
326 }
327
328 return arr;
329}
330
331PackedStringArray XRServer::get_suggested_pose_names(const StringName &p_tracker_name) const {
332 PackedStringArray arr;
333
334 for (int i = 0; i < interfaces.size(); i++) {
335 Ref<XRInterface> interface = interfaces[i];
336 PackedStringArray interface_arr = interface->get_suggested_pose_names(p_tracker_name);
337 for (int a = 0; a < interface_arr.size(); a++) {
338 if (!arr.has(interface_arr[a])) {
339 arr.push_back(interface_arr[a]);
340 }
341 }
342 }
343
344 if (arr.size() == 0) {
345 // no suggestions from our tracker? include our defaults
346 arr.push_back(String("default"));
347
348 if ((p_tracker_name == "left_hand") || (p_tracker_name == "right_hand")) {
349 arr.push_back(String("aim"));
350 arr.push_back(String("grip"));
351 arr.push_back(String("skeleton"));
352 }
353 }
354
355 return arr;
356}
357
358void XRServer::_process() {
359 // called from our main game loop before we handle physics and game logic
360 // note that we can have multiple interfaces active if we have interfaces that purely handle tracking
361
362 // process all active interfaces
363 for (int i = 0; i < interfaces.size(); i++) {
364 if (!interfaces[i].is_valid()) {
365 // ignore, not a valid reference
366 } else if (interfaces[i]->is_initialized()) {
367 interfaces.write[i]->process();
368 };
369 };
370};
371
372void XRServer::pre_render() {
373 // called from RendererViewport.draw_viewports right before we start drawing our viewports
374 // note that we can have multiple interfaces active if we have interfaces that purely handle tracking
375
376 // process all active interfaces
377 for (int i = 0; i < interfaces.size(); i++) {
378 if (!interfaces[i].is_valid()) {
379 // ignore, not a valid reference
380 } else if (interfaces[i]->is_initialized()) {
381 interfaces.write[i]->pre_render();
382 };
383 };
384}
385
386void XRServer::end_frame() {
387 // called from RenderingServerDefault after Vulkan queues have been submitted
388
389 // process all active interfaces
390 for (int i = 0; i < interfaces.size(); i++) {
391 if (!interfaces[i].is_valid()) {
392 // ignore, not a valid reference
393 } else if (interfaces[i]->is_initialized()) {
394 interfaces.write[i]->end_frame();
395 };
396 };
397}
398
399XRServer::XRServer() {
400 singleton = this;
401 world_scale = 1.0;
402};
403
404XRServer::~XRServer() {
405 primary_interface.unref();
406
407 while (interfaces.size() > 0) {
408 interfaces.remove_at(0);
409 }
410
411 // TODO pretty sure there is a clear function or something...
412 while (trackers.size() > 0) {
413 trackers.erase(trackers.get_key_at_index(0));
414 }
415
416 singleton = nullptr;
417};
418