536 lines
17 KiB
C
536 lines
17 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup wm
|
|
*
|
|
* \name Window-Manager XR Actions
|
|
*
|
|
* Uses the Ghost-XR API to manage OpenXR actions.
|
|
* All functions are designed to be usable by RNA / the Python API.
|
|
*/
|
|
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math.h"
|
|
|
|
#include "GHOST_C-api.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "wm_xr_intern.h"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name XR-Action API
|
|
*
|
|
* API functions for managing OpenXR actions.
|
|
*
|
|
* \{ */
|
|
|
|
static wmXrActionSet *action_set_create(const char *action_set_name)
|
|
{
|
|
wmXrActionSet *action_set = MEM_callocN(sizeof(*action_set), __func__);
|
|
action_set->name = MEM_mallocN(strlen(action_set_name) + 1, "XrActionSet_Name");
|
|
strcpy(action_set->name, action_set_name);
|
|
|
|
return action_set;
|
|
}
|
|
|
|
static void action_set_destroy(void *val)
|
|
{
|
|
wmXrActionSet *action_set = val;
|
|
|
|
MEM_SAFE_FREE(action_set->name);
|
|
|
|
BLI_freelistN(&action_set->active_modal_actions);
|
|
BLI_freelistN(&action_set->active_haptic_actions);
|
|
|
|
MEM_freeN(action_set);
|
|
}
|
|
|
|
static wmXrActionSet *action_set_find(wmXrData *xr, const char *action_set_name)
|
|
{
|
|
return GHOST_XrGetActionSetCustomdata(xr->runtime->context, action_set_name);
|
|
}
|
|
|
|
static wmXrAction *action_create(const char *action_name,
|
|
eXrActionType type,
|
|
const ListBase *user_paths,
|
|
wmOperatorType *ot,
|
|
IDProperty *op_properties,
|
|
const char *haptic_name,
|
|
const int64_t *haptic_duration,
|
|
const float *haptic_frequency,
|
|
const float *haptic_amplitude,
|
|
eXrOpFlag op_flag,
|
|
eXrActionFlag action_flag,
|
|
eXrHapticFlag haptic_flag)
|
|
{
|
|
wmXrAction *action = MEM_callocN(sizeof(*action), __func__);
|
|
action->name = MEM_mallocN(strlen(action_name) + 1, "XrAction_Name");
|
|
strcpy(action->name, action_name);
|
|
action->type = type;
|
|
|
|
const unsigned int count = (unsigned int)BLI_listbase_count(user_paths);
|
|
unsigned int subaction_idx = 0;
|
|
action->count_subaction_paths = count;
|
|
|
|
action->subaction_paths = MEM_mallocN(sizeof(*action->subaction_paths) * count,
|
|
"XrAction_SubactionPaths");
|
|
LISTBASE_FOREACH_INDEX (XrUserPath *, user_path, user_paths, subaction_idx) {
|
|
action->subaction_paths[subaction_idx] = MEM_mallocN(strlen(user_path->path) + 1,
|
|
"XrAction_SubactionPath");
|
|
strcpy(action->subaction_paths[subaction_idx], user_path->path);
|
|
}
|
|
|
|
size_t size;
|
|
switch (type) {
|
|
case XR_BOOLEAN_INPUT:
|
|
size = sizeof(bool);
|
|
break;
|
|
case XR_FLOAT_INPUT:
|
|
size = sizeof(float);
|
|
break;
|
|
case XR_VECTOR2F_INPUT:
|
|
size = sizeof(float) * 2;
|
|
break;
|
|
case XR_POSE_INPUT:
|
|
size = sizeof(GHOST_XrPose);
|
|
break;
|
|
case XR_VIBRATION_OUTPUT:
|
|
return action;
|
|
}
|
|
action->states = MEM_calloc_arrayN(count, size, "XrAction_States");
|
|
action->states_prev = MEM_calloc_arrayN(count, size, "XrAction_StatesPrev");
|
|
|
|
const bool is_float_action = ELEM(type, XR_FLOAT_INPUT, XR_VECTOR2F_INPUT);
|
|
const bool is_button_action = (is_float_action || type == XR_BOOLEAN_INPUT);
|
|
if (is_float_action) {
|
|
action->float_thresholds = MEM_calloc_arrayN(
|
|
count, sizeof(*action->float_thresholds), "XrAction_FloatThresholds");
|
|
}
|
|
if (is_button_action) {
|
|
action->axis_flags = MEM_calloc_arrayN(
|
|
count, sizeof(*action->axis_flags), "XrAction_AxisFlags");
|
|
}
|
|
|
|
action->ot = ot;
|
|
action->op_properties = op_properties;
|
|
|
|
if (haptic_name) {
|
|
BLI_assert(is_button_action);
|
|
action->haptic_name = MEM_mallocN(strlen(haptic_name) + 1, "XrAction_HapticName");
|
|
strcpy(action->haptic_name, haptic_name);
|
|
action->haptic_duration = *haptic_duration;
|
|
action->haptic_frequency = *haptic_frequency;
|
|
action->haptic_amplitude = *haptic_amplitude;
|
|
}
|
|
|
|
action->op_flag = op_flag;
|
|
action->action_flag = action_flag;
|
|
action->haptic_flag = haptic_flag;
|
|
|
|
return action;
|
|
}
|
|
|
|
static void action_destroy(void *val)
|
|
{
|
|
wmXrAction *action = val;
|
|
|
|
MEM_SAFE_FREE(action->name);
|
|
|
|
char **subaction_paths = action->subaction_paths;
|
|
if (subaction_paths) {
|
|
for (unsigned int i = 0; i < action->count_subaction_paths; ++i) {
|
|
MEM_SAFE_FREE(subaction_paths[i]);
|
|
}
|
|
MEM_freeN(subaction_paths);
|
|
}
|
|
|
|
MEM_SAFE_FREE(action->states);
|
|
MEM_SAFE_FREE(action->states_prev);
|
|
|
|
MEM_SAFE_FREE(action->float_thresholds);
|
|
MEM_SAFE_FREE(action->axis_flags);
|
|
|
|
MEM_SAFE_FREE(action->haptic_name);
|
|
|
|
MEM_freeN(action);
|
|
}
|
|
|
|
static wmXrAction *action_find(wmXrData *xr, const char *action_set_name, const char *action_name)
|
|
{
|
|
return GHOST_XrGetActionCustomdata(xr->runtime->context, action_set_name, action_name);
|
|
}
|
|
|
|
bool WM_xr_action_set_create(wmXrData *xr, const char *action_set_name)
|
|
{
|
|
if (action_set_find(xr, action_set_name)) {
|
|
return false;
|
|
}
|
|
|
|
wmXrActionSet *action_set = action_set_create(action_set_name);
|
|
|
|
GHOST_XrActionSetInfo info = {
|
|
.name = action_set_name,
|
|
.customdata_free_fn = action_set_destroy,
|
|
.customdata = action_set,
|
|
};
|
|
|
|
if (!GHOST_XrCreateActionSet(xr->runtime->context, &info)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WM_xr_action_set_destroy(wmXrData *xr, const char *action_set_name)
|
|
{
|
|
wmXrActionSet *action_set = action_set_find(xr, action_set_name);
|
|
if (!action_set) {
|
|
return;
|
|
}
|
|
|
|
wmXrSessionState *session_state = &xr->runtime->session_state;
|
|
|
|
if (action_set == session_state->active_action_set) {
|
|
if (action_set->controller_grip_action || action_set->controller_aim_action) {
|
|
wm_xr_session_controller_data_clear(session_state);
|
|
action_set->controller_grip_action = action_set->controller_aim_action = NULL;
|
|
}
|
|
|
|
BLI_freelistN(&action_set->active_modal_actions);
|
|
BLI_freelistN(&action_set->active_haptic_actions);
|
|
|
|
session_state->active_action_set = NULL;
|
|
}
|
|
|
|
GHOST_XrDestroyActionSet(xr->runtime->context, action_set_name);
|
|
}
|
|
|
|
bool WM_xr_action_create(wmXrData *xr,
|
|
const char *action_set_name,
|
|
const char *action_name,
|
|
eXrActionType type,
|
|
const ListBase *user_paths,
|
|
wmOperatorType *ot,
|
|
IDProperty *op_properties,
|
|
const char *haptic_name,
|
|
const int64_t *haptic_duration,
|
|
const float *haptic_frequency,
|
|
const float *haptic_amplitude,
|
|
eXrOpFlag op_flag,
|
|
eXrActionFlag action_flag,
|
|
eXrHapticFlag haptic_flag)
|
|
{
|
|
if (action_find(xr, action_set_name, action_name)) {
|
|
return false;
|
|
}
|
|
|
|
wmXrAction *action = action_create(action_name,
|
|
type,
|
|
user_paths,
|
|
ot,
|
|
op_properties,
|
|
haptic_name,
|
|
haptic_duration,
|
|
haptic_frequency,
|
|
haptic_amplitude,
|
|
op_flag,
|
|
action_flag,
|
|
haptic_flag);
|
|
|
|
const unsigned int count = (unsigned int)BLI_listbase_count(user_paths);
|
|
unsigned int subaction_idx = 0;
|
|
|
|
char **subaction_paths = MEM_calloc_arrayN(
|
|
count, sizeof(*subaction_paths), "XrAction_SubactionPathPointers");
|
|
|
|
LISTBASE_FOREACH_INDEX (XrUserPath *, user_path, user_paths, subaction_idx) {
|
|
subaction_paths[subaction_idx] = (char *)user_path->path;
|
|
}
|
|
|
|
GHOST_XrActionInfo info = {
|
|
.name = action_name,
|
|
.count_subaction_paths = count,
|
|
.subaction_paths = (const char **)subaction_paths,
|
|
.states = action->states,
|
|
.float_thresholds = action->float_thresholds,
|
|
.axis_flags = (int16_t *)action->axis_flags,
|
|
.customdata_free_fn = action_destroy,
|
|
.customdata = action,
|
|
};
|
|
|
|
switch (type) {
|
|
case XR_BOOLEAN_INPUT:
|
|
info.type = GHOST_kXrActionTypeBooleanInput;
|
|
break;
|
|
case XR_FLOAT_INPUT:
|
|
info.type = GHOST_kXrActionTypeFloatInput;
|
|
break;
|
|
case XR_VECTOR2F_INPUT:
|
|
info.type = GHOST_kXrActionTypeVector2fInput;
|
|
break;
|
|
case XR_POSE_INPUT:
|
|
info.type = GHOST_kXrActionTypePoseInput;
|
|
break;
|
|
case XR_VIBRATION_OUTPUT:
|
|
info.type = GHOST_kXrActionTypeVibrationOutput;
|
|
break;
|
|
}
|
|
|
|
const bool success = GHOST_XrCreateActions(xr->runtime->context, action_set_name, 1, &info);
|
|
|
|
MEM_freeN(subaction_paths);
|
|
|
|
return success;
|
|
}
|
|
|
|
void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name)
|
|
{
|
|
wmXrActionSet *action_set = action_set_find(xr, action_set_name);
|
|
if (!action_set) {
|
|
return;
|
|
}
|
|
|
|
wmXrAction *action = action_find(xr, action_set_name, action_name);
|
|
if (!action) {
|
|
return;
|
|
}
|
|
|
|
if ((action_set->controller_grip_action &&
|
|
STREQ(action_set->controller_grip_action->name, action_name)) ||
|
|
(action_set->controller_aim_action &&
|
|
STREQ(action_set->controller_aim_action->name, action_name))) {
|
|
if (action_set == xr->runtime->session_state.active_action_set) {
|
|
wm_xr_session_controller_data_clear(&xr->runtime->session_state);
|
|
}
|
|
action_set->controller_grip_action = action_set->controller_aim_action = NULL;
|
|
}
|
|
|
|
LISTBASE_FOREACH (LinkData *, ld, &action_set->active_modal_actions) {
|
|
wmXrAction *active_modal_action = ld->data;
|
|
if (STREQ(active_modal_action->name, action_name)) {
|
|
BLI_freelinkN(&action_set->active_modal_actions, ld);
|
|
break;
|
|
}
|
|
}
|
|
|
|
LISTBASE_FOREACH_MUTABLE (wmXrHapticAction *, ha, &action_set->active_haptic_actions) {
|
|
if (STREQ(ha->action->name, action_name)) {
|
|
BLI_freelinkN(&action_set->active_haptic_actions, ha);
|
|
}
|
|
}
|
|
|
|
GHOST_XrDestroyActions(xr->runtime->context, action_set_name, 1, &action_name);
|
|
}
|
|
|
|
bool WM_xr_action_binding_create(wmXrData *xr,
|
|
const char *action_set_name,
|
|
const char *action_name,
|
|
const char *profile_path,
|
|
const ListBase *user_paths,
|
|
const ListBase *component_paths,
|
|
const float *float_thresholds,
|
|
const eXrAxisFlag *axis_flags,
|
|
const struct wmXrPose *poses)
|
|
{
|
|
const unsigned int count = (unsigned int)BLI_listbase_count(user_paths);
|
|
BLI_assert(count == (unsigned int)BLI_listbase_count(component_paths));
|
|
|
|
GHOST_XrActionBindingInfo *binding_infos = MEM_calloc_arrayN(
|
|
count, sizeof(*binding_infos), "XrActionBinding_Infos");
|
|
|
|
char **subaction_paths = MEM_calloc_arrayN(
|
|
count, sizeof(*subaction_paths), "XrActionBinding_SubactionPathPointers");
|
|
|
|
for (unsigned int i = 0; i < count; ++i) {
|
|
GHOST_XrActionBindingInfo *binding_info = &binding_infos[i];
|
|
const XrUserPath *user_path = BLI_findlink(user_paths, i);
|
|
const XrComponentPath *component_path = BLI_findlink(component_paths, i);
|
|
|
|
subaction_paths[i] = (char *)user_path->path;
|
|
|
|
binding_info->component_path = component_path->path;
|
|
if (float_thresholds) {
|
|
binding_info->float_threshold = float_thresholds[i];
|
|
}
|
|
if (axis_flags) {
|
|
binding_info->axis_flag = axis_flags[i];
|
|
}
|
|
if (poses) {
|
|
copy_v3_v3(binding_info->pose.position, poses[i].position);
|
|
copy_qt_qt(binding_info->pose.orientation_quat, poses[i].orientation_quat);
|
|
}
|
|
}
|
|
|
|
GHOST_XrActionProfileInfo profile_info = {
|
|
.action_name = action_name,
|
|
.profile_path = profile_path,
|
|
.count_subaction_paths = count,
|
|
.subaction_paths = (const char **)subaction_paths,
|
|
.bindings = binding_infos,
|
|
};
|
|
|
|
const bool success = GHOST_XrCreateActionBindings(
|
|
xr->runtime->context, action_set_name, 1, &profile_info);
|
|
|
|
MEM_freeN(subaction_paths);
|
|
MEM_freeN(binding_infos);
|
|
|
|
return success;
|
|
}
|
|
|
|
void WM_xr_action_binding_destroy(wmXrData *xr,
|
|
const char *action_set_name,
|
|
const char *action_name,
|
|
const char *profile_path)
|
|
{
|
|
GHOST_XrDestroyActionBindings(
|
|
xr->runtime->context, action_set_name, 1, &action_name, &profile_path);
|
|
}
|
|
|
|
bool WM_xr_active_action_set_set(wmXrData *xr, const char *action_set_name)
|
|
{
|
|
wmXrActionSet *action_set = action_set_find(xr, action_set_name);
|
|
if (!action_set) {
|
|
return false;
|
|
}
|
|
|
|
{
|
|
/* Clear any active modal/haptic actions. */
|
|
wmXrActionSet *active_action_set = xr->runtime->session_state.active_action_set;
|
|
if (active_action_set) {
|
|
BLI_freelistN(&active_action_set->active_modal_actions);
|
|
BLI_freelistN(&active_action_set->active_haptic_actions);
|
|
}
|
|
}
|
|
|
|
xr->runtime->session_state.active_action_set = action_set;
|
|
|
|
if (action_set->controller_grip_action && action_set->controller_aim_action) {
|
|
wm_xr_session_controller_data_populate(
|
|
action_set->controller_grip_action, action_set->controller_aim_action, xr);
|
|
}
|
|
else {
|
|
wm_xr_session_controller_data_clear(&xr->runtime->session_state);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WM_xr_controller_pose_actions_set(wmXrData *xr,
|
|
const char *action_set_name,
|
|
const char *grip_action_name,
|
|
const char *aim_action_name)
|
|
{
|
|
wmXrActionSet *action_set = action_set_find(xr, action_set_name);
|
|
if (!action_set) {
|
|
return false;
|
|
}
|
|
|
|
wmXrAction *grip_action = action_find(xr, action_set_name, grip_action_name);
|
|
if (!grip_action) {
|
|
return false;
|
|
}
|
|
|
|
wmXrAction *aim_action = action_find(xr, action_set_name, aim_action_name);
|
|
if (!aim_action) {
|
|
return false;
|
|
}
|
|
|
|
/* Ensure consistent subaction paths. */
|
|
const unsigned int count = grip_action->count_subaction_paths;
|
|
if (count != aim_action->count_subaction_paths) {
|
|
return false;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < count; ++i) {
|
|
if (!STREQ(grip_action->subaction_paths[i], aim_action->subaction_paths[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
action_set->controller_grip_action = grip_action;
|
|
action_set->controller_aim_action = aim_action;
|
|
|
|
if (action_set == xr->runtime->session_state.active_action_set) {
|
|
wm_xr_session_controller_data_populate(grip_action, aim_action, xr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WM_xr_action_state_get(const wmXrData *xr,
|
|
const char *action_set_name,
|
|
const char *action_name,
|
|
const char *subaction_path,
|
|
wmXrActionState *r_state)
|
|
{
|
|
const wmXrAction *action = action_find((wmXrData *)xr, action_set_name, action_name);
|
|
if (!action) {
|
|
return false;
|
|
}
|
|
|
|
r_state->type = (int)action->type;
|
|
|
|
/* Find the action state corresponding to the subaction path. */
|
|
for (unsigned int i = 0; i < action->count_subaction_paths; ++i) {
|
|
if (STREQ(subaction_path, action->subaction_paths[i])) {
|
|
switch (action->type) {
|
|
case XR_BOOLEAN_INPUT:
|
|
r_state->state_boolean = ((bool *)action->states)[i];
|
|
break;
|
|
case XR_FLOAT_INPUT:
|
|
r_state->state_float = ((float *)action->states)[i];
|
|
break;
|
|
case XR_VECTOR2F_INPUT:
|
|
copy_v2_v2(r_state->state_vector2f, ((float(*)[2])action->states)[i]);
|
|
break;
|
|
case XR_POSE_INPUT: {
|
|
const GHOST_XrPose *pose = &((GHOST_XrPose *)action->states)[i];
|
|
copy_v3_v3(r_state->state_pose.position, pose->position);
|
|
copy_qt_qt(r_state->state_pose.orientation_quat, pose->orientation_quat);
|
|
break;
|
|
}
|
|
case XR_VIBRATION_OUTPUT:
|
|
BLI_assert_unreachable();
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool WM_xr_haptic_action_apply(wmXrData *xr,
|
|
const char *action_set_name,
|
|
const char *action_name,
|
|
const char *subaction_path,
|
|
const int64_t *duration,
|
|
const float *frequency,
|
|
const float *amplitude)
|
|
{
|
|
return GHOST_XrApplyHapticAction(xr->runtime->context,
|
|
action_set_name,
|
|
action_name,
|
|
subaction_path,
|
|
duration,
|
|
frequency,
|
|
amplitude) ?
|
|
true :
|
|
false;
|
|
}
|
|
|
|
void WM_xr_haptic_action_stop(wmXrData *xr,
|
|
const char *action_set_name,
|
|
const char *action_name,
|
|
const char *subaction_path)
|
|
{
|
|
GHOST_XrStopHapticAction(xr->runtime->context, action_set_name, action_name, subaction_path);
|
|
}
|
|
|
|
/** \} */ /* XR-Action API */
|