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 | |
36 | XRServer::XRMode XRServer::xr_mode = XRMODE_DEFAULT; |
37 | |
38 | XRServer::XRMode XRServer::get_xr_mode() { |
39 | return xr_mode; |
40 | } |
41 | |
42 | void XRServer::set_xr_mode(XRServer::XRMode p_mode) { |
43 | xr_mode = p_mode; |
44 | } |
45 | |
46 | XRServer *XRServer::singleton = nullptr; |
47 | |
48 | XRServer *XRServer::get_singleton() { |
49 | return singleton; |
50 | }; |
51 | |
52 | void 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 | |
101 | double XRServer::get_world_scale() const { |
102 | return world_scale; |
103 | }; |
104 | |
105 | void 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 | |
115 | Transform3D XRServer::get_world_origin() const { |
116 | return world_origin; |
117 | }; |
118 | |
119 | void XRServer::set_world_origin(const Transform3D &p_world_origin) { |
120 | world_origin = p_world_origin; |
121 | }; |
122 | |
123 | Transform3D XRServer::get_reference_frame() const { |
124 | return reference_frame; |
125 | }; |
126 | |
127 | void 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 | |
167 | Transform3D 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 | |
175 | void 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 | |
189 | void 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 | |
208 | int XRServer::get_interface_count() const { |
209 | return interfaces.size(); |
210 | }; |
211 | |
212 | Ref<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 | |
218 | Ref<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 | |
227 | TypedArray<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 | |
242 | Ref<XRInterface> XRServer::get_primary_interface() const { |
243 | return primary_interface; |
244 | }; |
245 | |
246 | void 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 | |
257 | void 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 | |
273 | void 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 | |
286 | Dictionary 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 | |
299 | Ref<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 | |
308 | PackedStringArray 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 | |
331 | PackedStringArray 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 | |
358 | void 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 | |
372 | void 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 | |
386 | void 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 | |
399 | XRServer::XRServer() { |
400 | singleton = this; |
401 | world_scale = 1.0; |
402 | }; |
403 | |
404 | XRServer::~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 | |