Blender V5.0
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
8
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/* -------------------------------------------------------------------- */
23
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, &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 (space_ != XR_NULL_HANDLE) {
46 CHECK_XR_ASSERT(xrDestroySpace(space_));
47 }
48}
49
51{
52 return space_;
53}
54
56
57/* -------------------------------------------------------------------- */
61
63 XrSession session,
64 XrAction action,
65 GHOST_XrActionType type,
66 const GHOST_XrActionProfileInfo &info)
67{
68 CHECK_XR(xrStringToPath(instance, info.profile_path, &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 = 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 (bindings_.find(interaction_path) != 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 bindings_.insert({interaction_path, sbinding.binding});
108
109 if (subaction_data_.find(subaction_path_str) == subaction_data_.end()) {
110 std::map<std::string, GHOST_XrSubactionData>::iterator it =
111 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 profile_;
145}
146
148{
149 for (auto &[subaction_path_str, subaction] : 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(profile_);
161 if (it == r_bindings.end()) {
162 it = r_bindings.emplace(std::piecewise_construct, std::make_tuple(profile_), std::make_tuple())
163 .first;
164 }
165
166 std::vector<XrActionSuggestedBinding> &sbindings = it->second;
167
168 for (auto &[path, binding] : bindings_) {
169 XrActionSuggestedBinding sbinding;
170 sbinding.action = action;
171 sbinding.binding = binding;
172
173 sbindings.push_back(std::move(sbinding));
174 }
175}
176
178
179/* -------------------------------------------------------------------- */
183
185 XrActionSet action_set,
186 const GHOST_XrActionInfo &info)
187 : type_(info.type),
188 states_(info.states),
189 float_thresholds_(info.float_thresholds),
190 axis_flags_(info.axis_flags),
191 custom_data_(
192 std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn))
193{
194 subaction_paths_.resize(info.count_subaction_paths);
195
196 for (uint32_t i = 0; i < info.count_subaction_paths; ++i) {
197 const char *subaction_path_str = info.subaction_paths[i];
198 CHECK_XR(xrStringToPath(instance, subaction_path_str, &subaction_paths_[i]),
199 (std::string("Failed to get user path \"") + subaction_path_str + "\".").data());
200 subaction_indices_.insert({subaction_path_str, i});
201 }
202
203 XrActionCreateInfo action_info{XR_TYPE_ACTION_CREATE_INFO};
204 strcpy(action_info.actionName, info.name);
205
206 /* Just use same name for localized. This can be changed in the future if necessary. */
207 strcpy(action_info.localizedActionName, info.name);
208
209 switch (info.type) {
210 case GHOST_kXrActionTypeBooleanInput:
211 action_info.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
212 break;
213 case GHOST_kXrActionTypeFloatInput:
214 action_info.actionType = XR_ACTION_TYPE_FLOAT_INPUT;
215 break;
216 case GHOST_kXrActionTypeVector2fInput:
217 action_info.actionType = XR_ACTION_TYPE_VECTOR2F_INPUT;
218 break;
219 case GHOST_kXrActionTypePoseInput:
220 action_info.actionType = XR_ACTION_TYPE_POSE_INPUT;
221 break;
222 case GHOST_kXrActionTypeVibrationOutput:
223 action_info.actionType = XR_ACTION_TYPE_VIBRATION_OUTPUT;
224 break;
225 }
226 action_info.countSubactionPaths = info.count_subaction_paths;
227 action_info.subactionPaths = subaction_paths_.data();
228
229 CHECK_XR(xrCreateAction(action_set, &action_info, &action_),
230 (std::string("Failed to create action \"") + info.name +
231 "\". Action name and/or paths are invalid. Name must not contain upper "
232 "case letters or special characters other than '-', '_', or '.'.")
233 .data());
234}
235
237{
238 if (action_ != XR_NULL_HANDLE) {
239 CHECK_XR_ASSERT(xrDestroyAction(action_));
240 }
241}
242
243bool GHOST_XrAction::createBinding(XrInstance instance,
244 XrSession session,
245 const GHOST_XrActionProfileInfo &info)
246{
247 if (profiles_.find(info.profile_path) != profiles_.end()) {
248 return false;
249 }
250
251 profiles_.emplace(std::piecewise_construct,
252 std::make_tuple(info.profile_path),
253 std::make_tuple(instance, session, action_, type_, info));
254
255 return true;
256}
257
258void GHOST_XrAction::destroyBinding(const char *profile_path)
259{
260 /* It's possible nothing is removed. */
261 profiles_.erase(profile_path);
262}
263
264void GHOST_XrAction::updateState(XrSession session,
265 const char *action_name,
266 XrSpace reference_space,
267 const XrTime &predicted_display_time)
268{
269 const bool is_float_action = (type_ == GHOST_kXrActionTypeFloatInput ||
270 type_ == GHOST_kXrActionTypeVector2fInput);
271 const bool is_button_action = (is_float_action || type_ == GHOST_kXrActionTypeBooleanInput);
272
273 XrActionStateGetInfo state_info{XR_TYPE_ACTION_STATE_GET_INFO};
274 state_info.action = action_;
275
276 const size_t count_subaction_paths = subaction_paths_.size();
277 for (size_t subaction_idx = 0; subaction_idx < count_subaction_paths; ++subaction_idx) {
278 state_info.subactionPath = subaction_paths_[subaction_idx];
279
280 /* Set subaction data based on current interaction profile. */
281 XrInteractionProfileState profile_state{XR_TYPE_INTERACTION_PROFILE_STATE};
282 CHECK_XR(xrGetCurrentInteractionProfile(session, state_info.subactionPath, &profile_state),
283 "Failed to get current interaction profile.");
284
285 const GHOST_XrSubactionData *subaction = nullptr;
286 for (auto &[profile_path, profile] : profiles_) {
287 if (profile.getProfile() == profile_state.interactionProfile) {
288 subaction = profile.getSubaction(state_info.subactionPath);
289 break;
290 }
291 }
292
293 if (subaction != nullptr) {
294 if (is_float_action) {
295 float_thresholds_[subaction_idx] = subaction->float_threshold;
296 }
297 if (is_button_action) {
298 axis_flags_[subaction_idx] = subaction->axis_flag;
299 }
300 }
301
302 switch (type_) {
303 case GHOST_kXrActionTypeBooleanInput: {
304 XrActionStateBoolean state{XR_TYPE_ACTION_STATE_BOOLEAN};
305 CHECK_XR(xrGetActionStateBoolean(session, &state_info, &state),
306 (std::string("Failed to get state for boolean action \"") + action_name + "\".")
307 .data());
308 if (state.isActive) {
309 ((bool *)states_)[subaction_idx] = state.currentState;
310 }
311 break;
312 }
313 case GHOST_kXrActionTypeFloatInput: {
314 XrActionStateFloat state{XR_TYPE_ACTION_STATE_FLOAT};
315 CHECK_XR(
316 xrGetActionStateFloat(session, &state_info, &state),
317 (std::string("Failed to get state for float action \"") + action_name + "\".").data());
318 if (state.isActive) {
319 ((float *)states_)[subaction_idx] = state.currentState;
320 }
321 break;
322 }
323 case GHOST_kXrActionTypeVector2fInput: {
324 XrActionStateVector2f state{XR_TYPE_ACTION_STATE_VECTOR2F};
325 CHECK_XR(xrGetActionStateVector2f(session, &state_info, &state),
326 (std::string("Failed to get state for vector2f action \"") + action_name + "\".")
327 .data());
328 if (state.isActive) {
329 memcpy(((float (*)[2])states_)[subaction_idx], &state.currentState, sizeof(float[2]));
330 }
331 break;
332 }
333 case GHOST_kXrActionTypePoseInput: {
334 /* Check for valid display time to avoid an error in #xrLocateSpace(). */
335 if (predicted_display_time > 0) {
336 XrActionStatePose state{XR_TYPE_ACTION_STATE_POSE};
337 CHECK_XR(xrGetActionStatePose(session, &state_info, &state),
338 (std::string("Failed to get state for pose action \"") + action_name + "\".")
339 .data());
340 ((GHOST_XrPose *)states_)[subaction_idx].is_active = state.isActive;
341 if (state.isActive) {
342 XrSpace pose_space = ((subaction != nullptr) && (subaction->space != nullptr)) ?
343 subaction->space->getSpace() :
344 XR_NULL_HANDLE;
345 if (pose_space != XR_NULL_HANDLE) {
346 XrSpaceLocation space_location{XR_TYPE_SPACE_LOCATION};
347 CHECK_XR(
348 xrLocateSpace(
349 pose_space, reference_space, predicted_display_time, &space_location),
350 (std::string("Failed to query pose space for action \"") + action_name + "\".")
351 .data());
352 copy_openxr_pose_to_ghost_pose(space_location.pose,
353 ((GHOST_XrPose *)states_)[subaction_idx]);
354 }
355 }
356 }
357 break;
358 }
359 case GHOST_kXrActionTypeVibrationOutput: {
360 break;
361 }
362 }
363 }
364}
365
367 const char *action_name,
368 const char *subaction_path_str,
369 const int64_t &duration,
370 const float &frequency,
371 const float &amplitude)
372{
373 XrHapticVibration vibration{XR_TYPE_HAPTIC_VIBRATION};
374 vibration.duration = (duration == 0) ? XR_MIN_HAPTIC_DURATION :
375 static_cast<XrDuration>(duration);
376 vibration.frequency = frequency;
377 vibration.amplitude = amplitude;
378
379 XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO};
380 haptic_info.action = action_;
381
382 if (subaction_path_str != nullptr) {
383 SubactionIndexMap::iterator it = subaction_indices_.find(subaction_path_str);
384 if (it != subaction_indices_.end()) {
385 haptic_info.subactionPath = subaction_paths_[it->second];
386 CHECK_XR(
387 xrApplyHapticFeedback(session, &haptic_info, (const XrHapticBaseHeader *)&vibration),
388 (std::string("Failed to apply haptic action \"") + action_name + "\".").data());
389 }
390 }
391 else {
392 for (const XrPath &subaction_path : subaction_paths_) {
393 haptic_info.subactionPath = subaction_path;
394 CHECK_XR(
395 xrApplyHapticFeedback(session, &haptic_info, (const XrHapticBaseHeader *)&vibration),
396 (std::string("Failed to apply haptic action \"") + action_name + "\".").data());
397 }
398 }
399}
400
402 const char *action_name,
403 const char *subaction_path_str)
404{
405 XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO};
406 haptic_info.action = action_;
407
408 if (subaction_path_str != nullptr) {
409 SubactionIndexMap::iterator it = subaction_indices_.find(subaction_path_str);
410 if (it != subaction_indices_.end()) {
411 haptic_info.subactionPath = subaction_paths_[it->second];
412 CHECK_XR(xrStopHapticFeedback(session, &haptic_info),
413 (std::string("Failed to stop haptic action \"") + action_name + "\".").data());
414 }
415 }
416 else {
417 for (const XrPath &subaction_path : subaction_paths_) {
418 haptic_info.subactionPath = subaction_path;
419 CHECK_XR(xrStopHapticFeedback(session, &haptic_info),
420 (std::string("Failed to stop haptic action \"") + action_name + "\".").data());
421 }
422 }
423}
424
426{
427 if (custom_data_ == nullptr) {
428 return nullptr;
429 }
430 return custom_data_->custom_data_;
431}
432
434 std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const
435{
436 for (auto &[path, profile] : profiles_) {
437 profile.getBindings(action_, r_bindings);
438 }
439}
440
442
443/* -------------------------------------------------------------------- */
447
448GHOST_XrActionSet::GHOST_XrActionSet(XrInstance instance, const GHOST_XrActionSetInfo &info)
449 : custom_data_(
450 std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn))
451{
452 XrActionSetCreateInfo action_set_info{XR_TYPE_ACTION_SET_CREATE_INFO};
453 strcpy(action_set_info.actionSetName, info.name);
454
455 /* Just use same name for localized. This can be changed in the future if necessary. */
456 strcpy(action_set_info.localizedActionSetName, info.name);
457
458 action_set_info.priority = 0; /* Use same (default) priority for all action sets. */
459
460 CHECK_XR(xrCreateActionSet(instance, &action_set_info, &action_set_),
461 (std::string("Failed to create action set \"") + info.name +
462 "\". Name must not contain upper case letters or special characters "
463 "other than '-', '_', or '.'.")
464 .data());
465}
466
468{
469 /* This needs to be done before xrDestroyActionSet() to avoid an assertion in the GHOST_XrAction
470 * destructor (which calls xrDestroyAction()). */
471 actions_.clear();
472
473 if (action_set_ != XR_NULL_HANDLE) {
474 CHECK_XR_ASSERT(xrDestroyActionSet(action_set_));
475 }
476}
477
478bool GHOST_XrActionSet::createAction(XrInstance instance, const GHOST_XrActionInfo &info)
479{
480 if (actions_.find(info.name) != actions_.end()) {
481 return false;
482 }
483
484 actions_.emplace(std::piecewise_construct,
485 std::make_tuple(info.name),
486 std::make_tuple(instance, action_set_, info));
487
488 return true;
489}
490
491void GHOST_XrActionSet::destroyAction(const char *action_name)
492{
493 /* It's possible nothing is removed. */
494 actions_.erase(action_name);
495}
496
498{
499 std::map<std::string, GHOST_XrAction>::iterator it = actions_.find(action_name);
500 if (it == actions_.end()) {
501 return nullptr;
502 }
503 return &it->second;
504}
505
506void GHOST_XrActionSet::updateStates(XrSession session,
507 XrSpace reference_space,
508 const XrTime &predicted_display_time)
509{
510 for (auto &[name, action] : actions_) {
511 action.updateState(session, name.data(), reference_space, predicted_display_time);
512 }
513}
514
516{
517 return action_set_;
518}
519
521{
522 if (custom_data_ == nullptr) {
523 return nullptr;
524 }
525 return custom_data_->custom_data_;
526}
527
529{
530 return uint32_t(actions_.size());
531}
532
533void GHOST_XrActionSet::getActionCustomdataArray(void **r_customdata_array)
534{
535 uint32_t i = 0;
536 for (auto &[name, action] : actions_) {
537 r_customdata_array[i++] = action.getCustomdata();
538 }
539}
540
542 std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const
543{
544 for (auto &[name, action] : actions_) {
545 action.getBindings(r_bindings);
546 }
547}
548
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)
BMesh const char void * data
long long int int64_t
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]
const char * name
std::unique_ptr< GHOST_XrActionSpace > space
i
Definition text_draw.cc:230