Blender V4.3
GHOST_XrAction.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2021-2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include <cassert>
10#include <cstring>
11
12#include "GHOST_Types.h"
13
14#include "GHOST_XrException.hh"
15#include "GHOST_Xr_intern.hh"
16
17#include "GHOST_XrAction.hh"
18
19/* -------------------------------------------------------------------- */
25 XrAction action,
26 const char *action_name,
27 const char *profile_path,
28 XrPath subaction_path,
29 const char *subaction_path_str,
30 const GHOST_XrPose &pose)
31{
32 XrActionSpaceCreateInfo action_space_info{XR_TYPE_ACTION_SPACE_CREATE_INFO};
33 action_space_info.action = action;
34 action_space_info.subactionPath = subaction_path;
35 copy_ghost_pose_to_openxr_pose(pose, action_space_info.poseInActionSpace);
36
37 CHECK_XR(xrCreateActionSpace(session, &action_space_info, &m_space),
38 (std::string("Failed to create space \"") + subaction_path_str + "\" for action \"" +
39 action_name + "\" and profile \"" + profile_path + "\".")
40 .data());
41}
42
44{
45 if (m_space != XR_NULL_HANDLE) {
46 CHECK_XR_ASSERT(xrDestroySpace(m_space));
47 }
48}
49
51{
52 return m_space;
53}
54
57/* -------------------------------------------------------------------- */
63 XrSession session,
64 XrAction action,
65 GHOST_XrActionType type,
66 const GHOST_XrActionProfileInfo &info)
67{
68 CHECK_XR(xrStringToPath(instance, info.profile_path, &m_profile),
69 (std::string("Failed to get interaction profile path \"") + info.profile_path + "\".")
70 .data());
71
72 const bool is_float_action = (type == GHOST_kXrActionTypeFloatInput ||
73 type == GHOST_kXrActionTypeVector2fInput);
74 const bool is_button_action = (is_float_action || type == GHOST_kXrActionTypeBooleanInput);
75 const bool is_pose_action = (type == GHOST_kXrActionTypePoseInput);
76
77 /* Create bindings. */
78 XrInteractionProfileSuggestedBinding bindings_info{
79 XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING};
80 bindings_info.interactionProfile = m_profile;
81 bindings_info.countSuggestedBindings = 1;
82
83 for (uint32_t subaction_idx = 0; subaction_idx < info.count_subaction_paths; ++subaction_idx) {
84 const char *subaction_path_str = info.subaction_paths[subaction_idx];
85 const GHOST_XrActionBindingInfo &binding_info = info.bindings[subaction_idx];
86
87 const std::string interaction_path = std::string(subaction_path_str) +
88 binding_info.component_path;
89 if (m_bindings.find(interaction_path) != m_bindings.end()) {
90 continue;
91 }
92
93 XrActionSuggestedBinding sbinding;
94 sbinding.action = action;
95 CHECK_XR(xrStringToPath(instance, interaction_path.data(), &sbinding.binding),
96 (std::string("Failed to get interaction path \"") + interaction_path + "\".").data());
97 bindings_info.suggestedBindings = &sbinding;
98
99 /* Although the bindings will be re-suggested in GHOST_XrSession::attachActionSets(), it
100 * greatly improves error checking to suggest them here first. */
101 CHECK_XR(xrSuggestInteractionProfileBindings(instance, &bindings_info),
102 (std::string("Failed to create binding for action \"") + info.action_name +
103 "\" and profile \"" + info.profile_path +
104 "\". Are the action and profile paths correct?")
105 .data());
106
107 m_bindings.insert({interaction_path, sbinding.binding});
108
109 if (m_subaction_data.find(subaction_path_str) == m_subaction_data.end()) {
110 std::map<std::string, GHOST_XrSubactionData>::iterator it =
111 m_subaction_data
112 .emplace(
113 std::piecewise_construct, std::make_tuple(subaction_path_str), std::make_tuple())
114 .first;
115 GHOST_XrSubactionData &subaction = it->second;
116
117 CHECK_XR(xrStringToPath(instance, subaction_path_str, &subaction.subaction_path),
118 (std::string("Failed to get user path \"") + subaction_path_str + "\".").data());
119
120 if (is_float_action || is_button_action) {
121 if (is_float_action) {
122 subaction.float_threshold = binding_info.float_threshold;
123 }
124 if (is_button_action) {
125 subaction.axis_flag = binding_info.axis_flag;
126 }
127 }
128 else if (is_pose_action) {
129 /* Create action space for pose bindings. */
130 subaction.space = std::make_unique<GHOST_XrActionSpace>(session,
131 action,
132 info.action_name,
133 info.profile_path,
134 subaction.subaction_path,
135 subaction_path_str,
136 binding_info.pose);
137 }
138 }
139 }
140}
141
143{
144 return m_profile;
145}
146
148{
149 for (auto &[subaction_path_str, subaction] : m_subaction_data) {
150 if (subaction.subaction_path == subaction_path) {
151 return &subaction;
152 }
153 }
154 return nullptr;
155}
156
158 XrAction action, std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const
159{
160 std::map<XrPath, std::vector<XrActionSuggestedBinding>>::iterator it = r_bindings.find(
161 m_profile);
162 if (it == r_bindings.end()) {
163 it = r_bindings
164 .emplace(std::piecewise_construct, std::make_tuple(m_profile), std::make_tuple())
165 .first;
166 }
167
168 std::vector<XrActionSuggestedBinding> &sbindings = it->second;
169
170 for (auto &[path, binding] : m_bindings) {
171 XrActionSuggestedBinding sbinding;
172 sbinding.action = action;
173 sbinding.binding = binding;
174
175 sbindings.push_back(std::move(sbinding));
176 }
177}
178
181/* -------------------------------------------------------------------- */
187 XrActionSet action_set,
188 const GHOST_XrActionInfo &info)
189 : m_type(info.type),
190 m_states(info.states),
191 m_float_thresholds(info.float_thresholds),
192 m_axis_flags(info.axis_flags),
193 m_custom_data_(
194 std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn))
195{
196 m_subaction_paths.resize(info.count_subaction_paths);
197
198 for (uint32_t i = 0; i < info.count_subaction_paths; ++i) {
199 const char *subaction_path_str = info.subaction_paths[i];
200 CHECK_XR(xrStringToPath(instance, subaction_path_str, &m_subaction_paths[i]),
201 (std::string("Failed to get user path \"") + subaction_path_str + "\".").data());
202 m_subaction_indices.insert({subaction_path_str, i});
203 }
204
205 XrActionCreateInfo action_info{XR_TYPE_ACTION_CREATE_INFO};
206 strcpy(action_info.actionName, info.name);
207
208 /* Just use same name for localized. This can be changed in the future if necessary. */
209 strcpy(action_info.localizedActionName, info.name);
210
211 switch (info.type) {
212 case GHOST_kXrActionTypeBooleanInput:
213 action_info.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
214 break;
215 case GHOST_kXrActionTypeFloatInput:
216 action_info.actionType = XR_ACTION_TYPE_FLOAT_INPUT;
217 break;
218 case GHOST_kXrActionTypeVector2fInput:
219 action_info.actionType = XR_ACTION_TYPE_VECTOR2F_INPUT;
220 break;
221 case GHOST_kXrActionTypePoseInput:
222 action_info.actionType = XR_ACTION_TYPE_POSE_INPUT;
223 break;
224 case GHOST_kXrActionTypeVibrationOutput:
225 action_info.actionType = XR_ACTION_TYPE_VIBRATION_OUTPUT;
226 break;
227 }
228 action_info.countSubactionPaths = info.count_subaction_paths;
229 action_info.subactionPaths = m_subaction_paths.data();
230
231 CHECK_XR(xrCreateAction(action_set, &action_info, &m_action),
232 (std::string("Failed to create action \"") + info.name +
233 "\". Action name and/or paths are invalid. Name must not contain upper "
234 "case letters or special characters other than '-', '_', or '.'.")
235 .data());
236}
237
239{
240 if (m_action != XR_NULL_HANDLE) {
241 CHECK_XR_ASSERT(xrDestroyAction(m_action));
242 }
243}
244
245bool GHOST_XrAction::createBinding(XrInstance instance,
246 XrSession session,
247 const GHOST_XrActionProfileInfo &info)
248{
249 if (m_profiles.find(info.profile_path) != m_profiles.end()) {
250 return false;
251 }
252
253 m_profiles.emplace(std::piecewise_construct,
254 std::make_tuple(info.profile_path),
255 std::make_tuple(instance, session, m_action, m_type, info));
256
257 return true;
258}
259
260void GHOST_XrAction::destroyBinding(const char *profile_path)
261{
262 /* It's possible nothing is removed. */
263 m_profiles.erase(profile_path);
264}
265
266void GHOST_XrAction::updateState(XrSession session,
267 const char *action_name,
268 XrSpace reference_space,
269 const XrTime &predicted_display_time)
270{
271 const bool is_float_action = (m_type == GHOST_kXrActionTypeFloatInput ||
272 m_type == GHOST_kXrActionTypeVector2fInput);
273 const bool is_button_action = (is_float_action || m_type == GHOST_kXrActionTypeBooleanInput);
274
275 XrActionStateGetInfo state_info{XR_TYPE_ACTION_STATE_GET_INFO};
276 state_info.action = m_action;
277
278 const size_t count_subaction_paths = m_subaction_paths.size();
279 for (size_t subaction_idx = 0; subaction_idx < count_subaction_paths; ++subaction_idx) {
280 state_info.subactionPath = m_subaction_paths[subaction_idx];
281
282 /* Set subaction data based on current interaction profile. */
283 XrInteractionProfileState profile_state{XR_TYPE_INTERACTION_PROFILE_STATE};
284 CHECK_XR(xrGetCurrentInteractionProfile(session, state_info.subactionPath, &profile_state),
285 "Failed to get current interaction profile.");
286
287 const GHOST_XrSubactionData *subaction = nullptr;
288 for (auto &[profile_path, profile] : m_profiles) {
289 if (profile.getProfile() == profile_state.interactionProfile) {
290 subaction = profile.getSubaction(state_info.subactionPath);
291 break;
292 }
293 }
294
295 if (subaction != nullptr) {
296 if (is_float_action) {
297 m_float_thresholds[subaction_idx] = subaction->float_threshold;
298 }
299 if (is_button_action) {
300 m_axis_flags[subaction_idx] = subaction->axis_flag;
301 }
302 }
303
304 switch (m_type) {
305 case GHOST_kXrActionTypeBooleanInput: {
306 XrActionStateBoolean state{XR_TYPE_ACTION_STATE_BOOLEAN};
307 CHECK_XR(xrGetActionStateBoolean(session, &state_info, &state),
308 (std::string("Failed to get state for boolean action \"") + action_name + "\".")
309 .data());
310 if (state.isActive) {
311 ((bool *)m_states)[subaction_idx] = state.currentState;
312 }
313 break;
314 }
315 case GHOST_kXrActionTypeFloatInput: {
316 XrActionStateFloat state{XR_TYPE_ACTION_STATE_FLOAT};
317 CHECK_XR(
318 xrGetActionStateFloat(session, &state_info, &state),
319 (std::string("Failed to get state for float action \"") + action_name + "\".").data());
320 if (state.isActive) {
321 ((float *)m_states)[subaction_idx] = state.currentState;
322 }
323 break;
324 }
325 case GHOST_kXrActionTypeVector2fInput: {
326 XrActionStateVector2f state{XR_TYPE_ACTION_STATE_VECTOR2F};
327 CHECK_XR(xrGetActionStateVector2f(session, &state_info, &state),
328 (std::string("Failed to get state for vector2f action \"") + action_name + "\".")
329 .data());
330 if (state.isActive) {
331 memcpy(((float(*)[2])m_states)[subaction_idx], &state.currentState, sizeof(float[2]));
332 }
333 break;
334 }
335 case GHOST_kXrActionTypePoseInput: {
336 /* Check for valid display time to avoid an error in #xrLocateSpace(). */
337 if (predicted_display_time > 0) {
338 XrActionStatePose state{XR_TYPE_ACTION_STATE_POSE};
339 CHECK_XR(xrGetActionStatePose(session, &state_info, &state),
340 (std::string("Failed to get state for pose action \"") + action_name + "\".")
341 .data());
342 ((GHOST_XrPose *)m_states)[subaction_idx].is_active = state.isActive;
343 if (state.isActive) {
344 XrSpace pose_space = ((subaction != nullptr) && (subaction->space != nullptr)) ?
345 subaction->space->getSpace() :
346 XR_NULL_HANDLE;
347 if (pose_space != XR_NULL_HANDLE) {
348 XrSpaceLocation space_location{XR_TYPE_SPACE_LOCATION};
349 CHECK_XR(
350 xrLocateSpace(
351 pose_space, reference_space, predicted_display_time, &space_location),
352 (std::string("Failed to query pose space for action \"") + action_name + "\".")
353 .data());
354 copy_openxr_pose_to_ghost_pose(space_location.pose,
355 ((GHOST_XrPose *)m_states)[subaction_idx]);
356 }
357 }
358 }
359 break;
360 }
361 case GHOST_kXrActionTypeVibrationOutput: {
362 break;
363 }
364 }
365 }
366}
367
369 const char *action_name,
370 const char *subaction_path_str,
371 const int64_t &duration,
372 const float &frequency,
373 const float &amplitude)
374{
375 XrHapticVibration vibration{XR_TYPE_HAPTIC_VIBRATION};
376 vibration.duration = (duration == 0) ? XR_MIN_HAPTIC_DURATION :
377 static_cast<XrDuration>(duration);
378 vibration.frequency = frequency;
379 vibration.amplitude = amplitude;
380
381 XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO};
382 haptic_info.action = m_action;
383
384 if (subaction_path_str != nullptr) {
385 SubactionIndexMap::iterator it = m_subaction_indices.find(subaction_path_str);
386 if (it != m_subaction_indices.end()) {
387 haptic_info.subactionPath = m_subaction_paths[it->second];
388 CHECK_XR(
389 xrApplyHapticFeedback(session, &haptic_info, (const XrHapticBaseHeader *)&vibration),
390 (std::string("Failed to apply haptic action \"") + action_name + "\".").data());
391 }
392 }
393 else {
394 for (const XrPath &subaction_path : m_subaction_paths) {
395 haptic_info.subactionPath = subaction_path;
396 CHECK_XR(
397 xrApplyHapticFeedback(session, &haptic_info, (const XrHapticBaseHeader *)&vibration),
398 (std::string("Failed to apply haptic action \"") + action_name + "\".").data());
399 }
400 }
401}
402
404 const char *action_name,
405 const char *subaction_path_str)
406{
407 XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO};
408 haptic_info.action = m_action;
409
410 if (subaction_path_str != nullptr) {
411 SubactionIndexMap::iterator it = m_subaction_indices.find(subaction_path_str);
412 if (it != m_subaction_indices.end()) {
413 haptic_info.subactionPath = m_subaction_paths[it->second];
414 CHECK_XR(xrStopHapticFeedback(session, &haptic_info),
415 (std::string("Failed to stop haptic action \"") + action_name + "\".").data());
416 }
417 }
418 else {
419 for (const XrPath &subaction_path : m_subaction_paths) {
420 haptic_info.subactionPath = subaction_path;
421 CHECK_XR(xrStopHapticFeedback(session, &haptic_info),
422 (std::string("Failed to stop haptic action \"") + action_name + "\".").data());
423 }
424 }
425}
426
428{
429 if (m_custom_data_ == nullptr) {
430 return nullptr;
431 }
432 return m_custom_data_->custom_data_;
433}
434
436 std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const
437{
438 for (auto &[path, profile] : m_profiles) {
439 profile.getBindings(m_action, r_bindings);
440 }
441}
442
445/* -------------------------------------------------------------------- */
450GHOST_XrActionSet::GHOST_XrActionSet(XrInstance instance, const GHOST_XrActionSetInfo &info)
451 : m_custom_data_(
452 std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn))
453{
454 XrActionSetCreateInfo action_set_info{XR_TYPE_ACTION_SET_CREATE_INFO};
455 strcpy(action_set_info.actionSetName, info.name);
456
457 /* Just use same name for localized. This can be changed in the future if necessary. */
458 strcpy(action_set_info.localizedActionSetName, info.name);
459
460 action_set_info.priority = 0; /* Use same (default) priority for all action sets. */
461
462 CHECK_XR(xrCreateActionSet(instance, &action_set_info, &m_action_set),
463 (std::string("Failed to create action set \"") + info.name +
464 "\". Name must not contain upper case letters or special characters "
465 "other than '-', '_', or '.'.")
466 .data());
467}
468
470{
471 /* This needs to be done before xrDestroyActionSet() to avoid an assertion in the GHOST_XrAction
472 * destructor (which calls xrDestroyAction()). */
473 m_actions.clear();
474
475 if (m_action_set != XR_NULL_HANDLE) {
476 CHECK_XR_ASSERT(xrDestroyActionSet(m_action_set));
477 }
478}
479
480bool GHOST_XrActionSet::createAction(XrInstance instance, const GHOST_XrActionInfo &info)
481{
482 if (m_actions.find(info.name) != m_actions.end()) {
483 return false;
484 }
485
486 m_actions.emplace(std::piecewise_construct,
487 std::make_tuple(info.name),
488 std::make_tuple(instance, m_action_set, info));
489
490 return true;
491}
492
493void GHOST_XrActionSet::destroyAction(const char *action_name)
494{
495 /* It's possible nothing is removed. */
496 m_actions.erase(action_name);
497}
498
500{
501 std::map<std::string, GHOST_XrAction>::iterator it = m_actions.find(action_name);
502 if (it == m_actions.end()) {
503 return nullptr;
504 }
505 return &it->second;
506}
507
508void GHOST_XrActionSet::updateStates(XrSession session,
509 XrSpace reference_space,
510 const XrTime &predicted_display_time)
511{
512 for (auto &[name, action] : m_actions) {
513 action.updateState(session, name.data(), reference_space, predicted_display_time);
514 }
515}
516
518{
519 return m_action_set;
520}
521
523{
524 if (m_custom_data_ == nullptr) {
525 return nullptr;
526 }
527 return m_custom_data_->custom_data_;
528}
529
531{
532 return uint32_t(m_actions.size());
533}
534
535void GHOST_XrActionSet::getActionCustomdataArray(void **r_customdata_array)
536{
537 uint32_t i = 0;
538 for (auto &[name, action] : m_actions) {
539 r_customdata_array[i++] = action.getCustomdata();
540 }
541}
542
544 std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const
545{
546 for (auto &[name, action] : m_actions) {
547 action.getBindings(r_bindings);
548 }
549}
550
void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose)
void copy_ghost_pose_to_openxr_pose(const GHOST_XrPose &ghost_pose, XrPosef &r_oxr_pose)
#define CHECK_XR_ASSERT(call)
#define CHECK_XR(call, error_msg)
const GHOST_XrSubactionData * getSubaction(XrPath subaction_path) const
XrPath getProfile() const
GHOST_XrActionProfile()=delete
void getBindings(XrAction action, std::map< XrPath, std::vector< XrActionSuggestedBinding > > &r_bindings) const
GHOST_XrActionSet()=delete
void updateStates(XrSession session, XrSpace reference_space, const XrTime &predicted_display_time)
GHOST_XrAction * findAction(const char *action_name)
bool createAction(XrInstance instance, const GHOST_XrActionInfo &info)
void getBindings(std::map< XrPath, std::vector< XrActionSuggestedBinding > > &r_bindings) const
XrActionSet getActionSet() const
void getActionCustomdataArray(void **r_customdata_array)
uint32_t getActionCount() const
void destroyAction(const char *action_name)
XrSpace getSpace() const
GHOST_XrActionSpace()=delete
bool createBinding(XrInstance instance, XrSession session, const GHOST_XrActionProfileInfo &info)
void updateState(XrSession session, const char *action_name, XrSpace reference_space, const XrTime &predicted_display_time)
void getBindings(std::map< XrPath, std::vector< XrActionSuggestedBinding > > &r_bindings) const
void destroyBinding(const char *profile_path)
void applyHapticFeedback(XrSession session, const char *action_name, const char *subaction_path_str, const int64_t &duration, const float &frequency, const float &amplitude)
void stopHapticFeedback(XrSession session, const char *action_name, const char *subaction_path_str)
GHOST_XrAction()=delete
static ulong state[N]
unsigned int uint32_t
Definition stdint.h:80
__int64 int64_t
Definition stdint.h:89
std::unique_ptr< GHOST_XrActionSpace > space