XR Controller Support Step 2: Action Maps

Addresses the remaining portions of T77137 (Python API for Controller
Interaction), which was partially completed by D10942.

Adds an XR "action maps" system for loading XR action data from a
Python script. Action maps are accessible via the Python API, and are used
to pass default actions to the VR session during the
xr_session_start_pre() callback.

Since action maps are stored only as runtime data, they will be
cleaned up with the rest of the VR runtime data on file read or exit.

Reviewed By: Julian Eisel, Hans Goudey

Differential Revision: https://developer.blender.org/D10943
This commit is contained in:
Peter Kim 2021-08-05 23:40:17 +09:00
parent 0cff7c2a22
commit e844e9e8f3
Notes: blender-bot 2023-02-13 11:53:18 +01:00
Referenced by commit cb9c0aa7d0, Fix: XR action map memory leaks
Referenced by commit 4c675bc356, Fix missing function param definition in rna_xr.c
14 changed files with 2508 additions and 70 deletions

View File

@ -1102,6 +1102,7 @@ int GHOST_XrSyncActions(GHOST_XrContextHandle xr_context, const char *action_set
int GHOST_XrApplyHapticAction(GHOST_XrContextHandle xr_context,
const char *action_set_name,
const char *action_name,
const char **subaction_path,
const int64_t *duration,
const float *frequency,
const float *amplitude);
@ -1111,7 +1112,8 @@ int GHOST_XrApplyHapticAction(GHOST_XrContextHandle xr_context,
*/
void GHOST_XrStopHapticAction(GHOST_XrContextHandle xr_context,
const char *action_set_name,
const char *action_name);
const char *action_name,
const char **subaction_path);
/**
* Get action set custom data (owned by Blender, not GHOST).
@ -1126,6 +1128,18 @@ void *GHOST_XrGetActionCustomdata(GHOST_XrContextHandle xr_context,
const char *action_set_name,
const char *action_name);
/**
* Get the number of actions in an action set.
*/
unsigned int GHOST_XrGetActionCount(GHOST_XrContextHandle xr_context, const char *action_set_name);
/**
* Get custom data for all actions in an action set.
*/
void GHOST_XrGetActionCustomdataArray(GHOST_XrContextHandle xr_context,
const char *action_set_name,
void **r_customdata_array);
#endif /* WITH_XR_OPENXR */
#ifdef __cplusplus

View File

@ -1005,25 +1005,29 @@ int GHOST_XrSyncActions(GHOST_XrContextHandle xr_contexthandle, const char *acti
int GHOST_XrApplyHapticAction(GHOST_XrContextHandle xr_contexthandle,
const char *action_set_name,
const char *action_name,
const char **subaction_path,
const int64_t *duration,
const float *frequency,
const float *amplitude)
{
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
GHOST_XrSession *xr_session = xr_context->getSession();
GHOST_XR_CAPI_CALL_RET(xr_session->applyHapticAction(
action_set_name, action_name, *duration, *frequency, *amplitude),
xr_context);
GHOST_XR_CAPI_CALL_RET(
xr_session->applyHapticAction(
action_set_name, action_name, subaction_path, *duration, *frequency, *amplitude),
xr_context);
return 0;
}
void GHOST_XrStopHapticAction(GHOST_XrContextHandle xr_contexthandle,
const char *action_set_name,
const char *action_name)
const char *action_name,
const char **subaction_path)
{
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
GHOST_XrSession *xr_session = xr_context->getSession();
GHOST_XR_CAPI_CALL(xr_session->stopHapticAction(action_set_name, action_name), xr_context);
GHOST_XR_CAPI_CALL(xr_session->stopHapticAction(action_set_name, action_name, subaction_path),
xr_context);
}
void *GHOST_XrGetActionSetCustomdata(GHOST_XrContextHandle xr_contexthandle,
@ -1046,4 +1050,23 @@ void *GHOST_XrGetActionCustomdata(GHOST_XrContextHandle xr_contexthandle,
return 0;
}
unsigned int GHOST_XrGetActionCount(GHOST_XrContextHandle xr_contexthandle,
const char *action_set_name)
{
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
GHOST_XrSession *xr_session = xr_context->getSession();
GHOST_XR_CAPI_CALL_RET(xr_session->getActionCount(action_set_name), xr_context);
return 0;
}
void GHOST_XrGetActionCustomdataArray(GHOST_XrContextHandle xr_contexthandle,
const char *action_set_name,
void **r_customdata_array)
{
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
GHOST_XrSession *xr_session = xr_context->getSession();
GHOST_XR_CAPI_CALL(xr_session->getActionCustomdataArray(action_set_name, r_customdata_array),
xr_context);
}
#endif /* WITH_XR_OPENXR */

View File

@ -208,8 +208,10 @@ GHOST_XrAction::GHOST_XrAction(XrInstance instance,
m_subaction_paths.resize(info.count_subaction_paths);
for (uint32_t i = 0; i < info.count_subaction_paths; ++i) {
CHECK_XR(xrStringToPath(instance, info.subaction_paths[i], &m_subaction_paths[i]),
(std::string("Failed to get user path \"") + info.subaction_paths[i] + "\".").data());
const char *subaction_path_str = info.subaction_paths[i];
CHECK_XR(xrStringToPath(instance, subaction_path_str, &m_subaction_paths[i]),
(std::string("Failed to get user path \"") + subaction_path_str + "\".").data());
m_subaction_indices.insert({subaction_path_str, i});
}
XrActionCreateInfo action_info{XR_TYPE_ACTION_CREATE_INFO};
@ -373,6 +375,7 @@ void GHOST_XrAction::updateState(XrSession session,
void GHOST_XrAction::applyHapticFeedback(XrSession session,
const char *action_name,
const char **subaction_path_str,
const int64_t &duration,
const float &frequency,
const float &amplitude)
@ -386,24 +389,46 @@ void GHOST_XrAction::applyHapticFeedback(XrSession session,
XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO};
haptic_info.action = m_action;
for (std::vector<XrPath>::iterator it = m_subaction_paths.begin(); it != m_subaction_paths.end();
++it) {
haptic_info.subactionPath = *it;
CHECK_XR(xrApplyHapticFeedback(session, &haptic_info, (const XrHapticBaseHeader *)&vibration),
(std::string("Failed to apply haptic action \"") + action_name + "\".").data());
if (subaction_path_str != nullptr) {
SubactionIndexMap::iterator it = m_subaction_indices.find(*subaction_path_str);
if (it != m_subaction_indices.end()) {
haptic_info.subactionPath = m_subaction_paths[it->second];
CHECK_XR(
xrApplyHapticFeedback(session, &haptic_info, (const XrHapticBaseHeader *)&vibration),
(std::string("Failed to apply haptic action \"") + action_name + "\".").data());
}
}
else {
for (const XrPath &subaction_path : m_subaction_paths) {
haptic_info.subactionPath = subaction_path;
CHECK_XR(
xrApplyHapticFeedback(session, &haptic_info, (const XrHapticBaseHeader *)&vibration),
(std::string("Failed to apply haptic action \"") + action_name + "\".").data());
}
}
}
void GHOST_XrAction::stopHapticFeedback(XrSession session, const char *action_name)
void GHOST_XrAction::stopHapticFeedback(XrSession session,
const char *action_name,
const char **subaction_path_str)
{
XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO};
haptic_info.action = m_action;
for (std::vector<XrPath>::iterator it = m_subaction_paths.begin(); it != m_subaction_paths.end();
++it) {
haptic_info.subactionPath = *it;
CHECK_XR(xrStopHapticFeedback(session, &haptic_info),
(std::string("Failed to stop haptic action \"") + action_name + "\".").data());
if (subaction_path_str != nullptr) {
SubactionIndexMap::iterator it = m_subaction_indices.find(*subaction_path_str);
if (it != m_subaction_indices.end()) {
haptic_info.subactionPath = m_subaction_paths[it->second];
CHECK_XR(xrStopHapticFeedback(session, &haptic_info),
(std::string("Failed to stop haptic action \"") + action_name + "\".").data());
}
}
else {
for (const XrPath &subaction_path : m_subaction_paths) {
haptic_info.subactionPath = subaction_path;
CHECK_XR(xrStopHapticFeedback(session, &haptic_info),
(std::string("Failed to stop haptic action \"") + action_name + "\".").data());
}
}
}
@ -511,6 +536,19 @@ void *GHOST_XrActionSet::getCustomdata()
return m_custom_data_->custom_data_;
}
uint32_t GHOST_XrActionSet::getActionCount() const
{
return (uint32_t)m_actions.size();
}
void GHOST_XrActionSet::getActionCustomdataArray(void **r_customdata_array)
{
uint32_t i = 0;
for (auto &[name, action] : m_actions) {
r_customdata_array[i++] = action.getCustomdata();
}
}
void GHOST_XrActionSet::getBindings(
std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const
{

View File

@ -103,17 +103,21 @@ class GHOST_XrAction {
const XrTime &predicted_display_time);
void applyHapticFeedback(XrSession session,
const char *action_name,
const char **subaction_path,
const int64_t &duration,
const float &frequency,
const float &amplitude);
void stopHapticFeedback(XrSession session, const char *action_name);
void stopHapticFeedback(XrSession session, const char *action_name, const char **subaction_path);
void *getCustomdata();
void getBindings(std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const;
private:
using SubactionIndexMap = std::map<std::string, uint32_t>;
XrAction m_action = XR_NULL_HANDLE;
GHOST_XrActionType m_type;
SubactionIndexMap m_subaction_indices;
std::vector<XrPath> m_subaction_paths;
/** States for each subaction path. */
void *m_states;
@ -145,6 +149,8 @@ class GHOST_XrActionSet {
XrActionSet getActionSet() const;
void *getCustomdata();
uint32_t getActionCount() const;
void getActionCustomdataArray(void **r_customdata_array);
void getBindings(std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const;
private:

View File

@ -754,6 +754,7 @@ bool GHOST_XrSession::syncActions(const char *action_set_name)
bool GHOST_XrSession::applyHapticAction(const char *action_set_name,
const char *action_name,
const char **subaction_path,
const int64_t &duration,
const float &frequency,
const float &amplitude)
@ -768,12 +769,15 @@ bool GHOST_XrSession::applyHapticAction(const char *action_set_name,
return false;
}
action->applyHapticFeedback(m_oxr->session, action_name, duration, frequency, amplitude);
action->applyHapticFeedback(
m_oxr->session, action_name, subaction_path, duration, frequency, amplitude);
return true;
}
void GHOST_XrSession::stopHapticAction(const char *action_set_name, const char *action_name)
void GHOST_XrSession::stopHapticAction(const char *action_set_name,
const char *action_name,
const char **subaction_path)
{
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
if (action_set == nullptr) {
@ -785,7 +789,7 @@ void GHOST_XrSession::stopHapticAction(const char *action_set_name, const char *
return;
}
action->stopHapticFeedback(m_oxr->session, action_name);
action->stopHapticFeedback(m_oxr->session, action_name, subaction_path);
}
void *GHOST_XrSession::getActionSetCustomdata(const char *action_set_name)
@ -813,4 +817,25 @@ void *GHOST_XrSession::getActionCustomdata(const char *action_set_name, const ch
return action->getCustomdata();
}
uint32_t GHOST_XrSession::getActionCount(const char *action_set_name)
{
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
if (action_set == nullptr) {
return 0;
}
return action_set->getActionCount();
}
void GHOST_XrSession::getActionCustomdataArray(const char *action_set_name,
void **r_customdata_array)
{
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
if (action_set == nullptr) {
return;
}
action_set->getActionCustomdataArray(r_customdata_array);
}
/** \} */ /* Actions */

View File

@ -76,14 +76,19 @@ class GHOST_XrSession {
bool syncActions(const char *action_set_name = nullptr);
bool applyHapticAction(const char *action_set_name,
const char *action_name,
const char **subaction_path,
const int64_t &duration,
const float &frequency,
const float &amplitude);
void stopHapticAction(const char *action_set_name, const char *action_name);
void stopHapticAction(const char *action_set_name,
const char *action_name,
const char **subaction_path);
/* Custom data (owned by Blender, not GHOST) accessors. */
void *getActionSetCustomdata(const char *action_set_name);
void *getActionCustomdata(const char *action_set_name, const char *action_name);
uint32_t getActionCount(const char *action_set_name);
void getActionCustomdataArray(const char *action_set_name, void **r_customdata_array);
private:
/** Pointer back to context managing this session. Would be nice to avoid, but needed to access

View File

@ -26,6 +26,8 @@
extern "C" {
#endif
/* -------------------------------------------------------------------- */
typedef struct XrSessionSettings {
/** Shading settings, struct shared with 3D-View so settings are the same. */
struct View3DShading shading;
@ -68,21 +70,122 @@ typedef enum eXrActionType {
XR_VIBRATION_OUTPUT = 100,
} eXrActionType;
/** Determines how XR action operators are executed. */
typedef enum eXrOpFlag {
XR_OP_PRESS = 0,
XR_OP_RELEASE = 1,
XR_OP_MODAL = 2,
} eXrOpFlag;
typedef enum eXrActionFlag {
/** Action depends on two subaction paths (i.e. two-handed/bimanual action). */
XR_ACTION_BIMANUAL = (1 << 0),
} eXrActionFlag;
typedef enum eXrHapticFlag {
/** Whether to apply haptics to corresponding user paths for an action and its haptic action. */
XR_HAPTIC_MATCHUSERPATHS = (1 << 0),
/** Determines how haptics will be applied ("repeat" is mutually exclusive with
"press"/"release"). */
XR_HAPTIC_PRESS = (1 << 1),
XR_HAPTIC_RELEASE = (1 << 2),
XR_HAPTIC_REPEAT = (1 << 3),
} eXrHapticFlag;
/** For axis-based inputs (thumbstick/trackpad/etc). Determines the region for action execution
* (mutually exclusive per axis). */
typedef enum eXrAxisFlag {
/** For axis-based inputs (thumbstick/trackpad/etc). Determines the region for action execution
(mutually exclusive per axis). */
XR_AXIS0_POS = (1 << 0),
XR_AXIS0_NEG = (1 << 1),
XR_AXIS1_POS = (1 << 2),
XR_AXIS1_NEG = (1 << 3),
} eXrAxisFlag;
typedef enum eXrPoseFlag {
/* Pose represents controller grip/aim. */
XR_POSE_GRIP = (1 << 0),
XR_POSE_AIM = (1 << 1),
} eXrPoseFlag;
/* -------------------------------------------------------------------- */
typedef struct XrActionMapBinding {
struct XrActionMapBinding *next, *prev;
/** Unique name. */
char name[64]; /* MAX_NAME */
/** OpenXR interaction profile path. */
char profile[256];
/** OpenXR component paths. */
char component_path0[192];
char component_path1[192];
/** Input threshold/region. */
float float_threshold;
short axis_flag; /* eXrAxisFlag */
char _pad[2];
/** Pose action properties. */
float pose_location[3];
float pose_rotation[3];
} XrActionMapBinding;
/* -------------------------------------------------------------------- */
typedef struct XrActionMapItem {
struct XrActionMapItem *next, *prev;
/** Unique name. */
char name[64]; /* MAX_NAME */
/** Type. */
char type; /** eXrActionType */
char _pad[7];
/** OpenXR user paths. */
char user_path0[64];
char user_path1[64];
/** Operator to be called on XR events. */
char op[64]; /* OP_MAX_TYPENAME */
/** Operator properties, assigned to ptr->data and can be written to a file. */
IDProperty *op_properties;
/** RNA pointer to access properties. */
struct PointerRNA *op_properties_ptr;
short op_flag; /* eXrOpFlag */
short action_flag; /* eXrActionFlag */
short haptic_flag; /* eXrHapticFlag */
/** Pose action properties. */
short pose_flag; /* eXrPoseFlag */
/** Haptic properties. */
char haptic_name[64]; /* MAX_NAME */
float haptic_duration;
float haptic_frequency;
float haptic_amplitude;
short selbinding;
char _pad3[2];
ListBase bindings; /* XrActionMapBinding */
} XrActionMapItem;
/* -------------------------------------------------------------------- */
typedef struct XrActionMap {
struct XrActionMap *next, *prev;
/** Unique name. */
char name[64]; /* MAX_NAME */
ListBase items; /* XrActionMapItem */
short selitem;
char _pad[6];
} XrActionMap;
/* -------------------------------------------------------------------- */
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -204,6 +204,7 @@ if(WITH_XR_OPENXR)
list(APPEND SRC
xr/intern/wm_xr.c
xr/intern/wm_xr_action.c
xr/intern/wm_xr_actionmap.c
xr/intern/wm_xr_draw.c
xr/intern/wm_xr_session.c

View File

@ -74,8 +74,7 @@ struct wmNDOFMotionData;
#endif
#ifdef WITH_XR_OPENXR
struct wmXrActionState;
struct wmXrPose;
struct wmXrRuntimeData;
#endif
typedef struct wmGizmo wmGizmo;
@ -988,7 +987,13 @@ bool WM_xr_action_create(wmXrData *xr,
const char **subaction_paths,
struct wmOperatorType *ot,
struct IDProperty *op_properties,
eXrOpFlag op_flag);
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);
void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name);
bool WM_xr_action_binding_create(wmXrData *xr,
const char *action_set_name,
@ -1022,10 +1027,48 @@ bool WM_xr_action_state_get(const wmXrData *xr,
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);
void WM_xr_haptic_action_stop(wmXrData *xr, const char *action_set_name, const char *action_name);
void WM_xr_haptic_action_stop(wmXrData *xr,
const char *action_set_name,
const char *action_name,
const char **subaction_path);
/* wm_xr_actionmap.c */
XrActionMap *WM_xr_actionmap_new(struct wmXrRuntimeData *runtime,
const char *name,
bool replace_existing);
void WM_xr_actionmap_ensure_unique(struct wmXrRuntimeData *runtime, XrActionMap *actionmap);
XrActionMap *WM_xr_actionmap_add_copy(struct wmXrRuntimeData *runtime, XrActionMap *am_src);
bool WM_xr_actionmap_remove(struct wmXrRuntimeData *runtime, XrActionMap *actionmap);
XrActionMap *WM_xr_actionmap_find(struct wmXrRuntimeData *runtime, const char *name);
void WM_xr_actionmap_clear(XrActionMap *actionmap);
void WM_xr_actionmaps_clear(struct wmXrRuntimeData *runtime);
ListBase *WM_xr_actionmaps_get(struct wmXrRuntimeData *runtime);
short WM_xr_actionmap_active_index_get(const struct wmXrRuntimeData *runtime);
void WM_xr_actionmap_active_index_set(struct wmXrRuntimeData *runtime, short idx);
short WM_xr_actionmap_selected_index_get(const struct wmXrRuntimeData *runtime);
void WM_xr_actionmap_selected_index_set(struct wmXrRuntimeData *runtime, short idx);
XrActionMapItem *WM_xr_actionmap_item_new(XrActionMap *actionmap,
const char *name,
bool replace_existing);
void WM_xr_actionmap_item_ensure_unique(XrActionMap *actionmap, XrActionMapItem *ami);
XrActionMapItem *WM_xr_actionmap_item_add_copy(XrActionMap *actionmap, XrActionMapItem *ami_src);
bool WM_xr_actionmap_item_remove(XrActionMap *actionmap, XrActionMapItem *ami);
XrActionMapItem *WM_xr_actionmap_item_find(XrActionMap *actionmap, const char *name);
void WM_xr_actionmap_item_properties_update_ot(XrActionMapItem *ami);
XrActionMapBinding *WM_xr_actionmap_binding_new(XrActionMapItem *ami,
const char *name,
bool replace_existing);
void WM_xr_actionmap_binding_ensure_unique(XrActionMapItem *ami, XrActionMapBinding *amb);
XrActionMapBinding *WM_xr_actionmap_binding_add_copy(XrActionMapItem *ami,
XrActionMapBinding *amb_src);
bool WM_xr_actionmap_binding_remove(XrActionMapItem *ami, XrActionMapBinding *amb);
XrActionMapBinding *WM_xr_actionmap_binding_find(XrActionMapItem *ami, const char *name);
#endif /* WITH_XR_OPENXR */
#ifdef __cplusplus

View File

@ -115,6 +115,7 @@ bool wm_xr_init(wmWindowManager *wm)
void wm_xr_exit(wmWindowManager *wm)
{
if (wm->xr.runtime != NULL) {
WM_xr_actionmaps_clear(wm->xr.runtime);
wm_xr_runtime_data_free(&wm->xr.runtime);
}
if (wm->xr.session_settings.shading.prop) {

View File

@ -23,6 +23,7 @@
* 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"
@ -56,6 +57,9 @@ static void action_set_destroy(void *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);
}
@ -70,7 +74,13 @@ static wmXrAction *action_create(const char *action_name,
const char **subaction_paths,
wmOperatorType *ot,
IDProperty *op_properties,
eXrOpFlag op_flag)
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");
@ -121,7 +131,19 @@ static wmXrAction *action_create(const char *action_name,
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;
}
@ -147,6 +169,8 @@ static void action_destroy(void *val)
MEM_SAFE_FREE(action->float_thresholds);
MEM_SAFE_FREE(action->axis_flags);
MEM_SAFE_FREE(action->haptic_name);
MEM_freeN(action);
}
@ -190,9 +214,10 @@ void WM_xr_action_set_destroy(wmXrData *xr, const char *action_set_name)
wm_xr_session_controller_data_clear(session_state);
action_set->controller_grip_action = action_set->controller_aim_action = NULL;
}
if (action_set->active_modal_action) {
action_set->active_modal_action = NULL;
}
BLI_freelistN(&action_set->active_modal_actions);
BLI_freelistN(&action_set->active_haptic_actions);
session_state->active_action_set = NULL;
}
@ -207,14 +232,31 @@ bool WM_xr_action_create(wmXrData *xr,
const char **subaction_paths,
wmOperatorType *ot,
IDProperty *op_properties,
eXrOpFlag op_flag)
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, count_subaction_paths, subaction_paths, ot, op_properties, op_flag);
wmXrAction *action = action_create(action_name,
type,
count_subaction_paths,
subaction_paths,
ot,
op_properties,
haptic_name,
haptic_duration,
haptic_frequency,
haptic_amplitude,
op_flag,
action_flag,
haptic_flag);
GHOST_XrActionInfo info = {
.name = action_name,
@ -274,9 +316,18 @@ void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char
action_set->controller_grip_action = action_set->controller_aim_action = NULL;
}
if (action_set->active_modal_action &&
STREQ(action_set->active_modal_action->name, action_name)) {
action_set->active_modal_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);
@ -342,16 +393,11 @@ bool WM_xr_active_action_set_set(wmXrData *xr, const char *action_set_name)
}
{
/* Unset active modal action (if any). */
/* Clear any active modal/haptic actions. */
wmXrActionSet *active_action_set = xr->runtime->session_state.active_action_set;
if (active_action_set) {
wmXrAction *active_modal_action = active_action_set->active_modal_action;
if (active_modal_action) {
if (active_modal_action->active_modal_path) {
active_modal_action->active_modal_path = NULL;
}
active_action_set->active_modal_action = NULL;
}
BLI_freelistN(&active_action_set->active_modal_actions);
BLI_freelistN(&active_action_set->active_haptic_actions);
}
}
@ -456,19 +502,28 @@ bool WM_xr_action_state_get(const wmXrData *xr,
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, duration, frequency, 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)
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);
GHOST_XrStopHapticAction(xr->runtime->context, action_set_name, action_name, subaction_path);
}
/** \} */ /* XR-Action API */

View File

@ -0,0 +1,565 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup wm
*
* \name Window-Manager XR Action Maps
*
* XR actionmap API, similar to WM keymap API.
*/
#include <math.h>
#include <string.h>
#include "BKE_context.h"
#include "BKE_idprop.h"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "GHOST_Types.h"
#include "MEM_guardedalloc.h"
#include "WM_api.h"
#include "WM_types.h"
#include "wm_xr_intern.h"
#define WM_XR_ACTIONMAP_STR_DEFAULT "actionmap"
#define WM_XR_ACTIONMAP_ITEM_STR_DEFAULT "action"
#define WM_XR_ACTIONMAP_BINDING_STR_DEFAULT "binding"
/* -------------------------------------------------------------------- */
/** \name Action Map Binding
*
* Binding in an XR action map item, that maps an action to an XR input.
* \{ */
XrActionMapBinding *WM_xr_actionmap_binding_new(XrActionMapItem *ami,
const char *name,
bool replace_existing)
{
XrActionMapBinding *amb_prev = WM_xr_actionmap_binding_find(ami, name);
if (amb_prev && replace_existing) {
return amb_prev;
}
XrActionMapBinding *amb = MEM_callocN(sizeof(XrActionMapBinding), __func__);
BLI_strncpy(amb->name, name, MAX_NAME);
if (amb_prev) {
WM_xr_actionmap_binding_ensure_unique(ami, amb);
}
BLI_addtail(&ami->bindings, amb);
/* Set non-zero threshold by default. */
amb->float_threshold = 0.3f;
return amb;
}
static XrActionMapBinding *wm_xr_actionmap_binding_find_except(XrActionMapItem *ami,
const char *name,
XrActionMapBinding *ambexcept)
{
LISTBASE_FOREACH (XrActionMapBinding *, amb, &ami->bindings) {
if (STREQLEN(name, amb->name, MAX_NAME) && (amb != ambexcept)) {
return amb;
}
}
return NULL;
}
/**
* Ensure unique name among all action map bindings.
*/
void WM_xr_actionmap_binding_ensure_unique(XrActionMapItem *ami, XrActionMapBinding *amb)
{
char name[MAX_NAME];
char *suffix;
size_t baselen;
size_t idx = 0;
BLI_strncpy(name, amb->name, MAX_NAME);
baselen = BLI_strnlen(name, MAX_NAME);
suffix = &name[baselen];
while (wm_xr_actionmap_binding_find_except(ami, name, amb)) {
if ((baselen + 1) + (log10(++idx) + 1) > MAX_NAME) {
/* Use default base name. */
BLI_strncpy(name, WM_XR_ACTIONMAP_BINDING_STR_DEFAULT, MAX_NAME);
baselen = BLI_strnlen(name, MAX_NAME);
suffix = &name[baselen];
idx = 0;
}
else {
BLI_snprintf(suffix, MAX_NAME, "%zu", idx);
}
}
BLI_strncpy(amb->name, name, MAX_NAME);
}
static XrActionMapBinding *wm_xr_actionmap_binding_copy(XrActionMapBinding *amb_src)
{
XrActionMapBinding *amb_dst = MEM_dupallocN(amb_src);
amb_dst->prev = amb_dst->next = NULL;
return amb_dst;
}
XrActionMapBinding *WM_xr_actionmap_binding_add_copy(XrActionMapItem *ami,
XrActionMapBinding *amb_src)
{
XrActionMapBinding *amb_dst = wm_xr_actionmap_binding_copy(amb_src);
WM_xr_actionmap_binding_ensure_unique(ami, amb_dst);
BLI_addtail(&ami->bindings, amb_dst);
return amb_dst;
}
bool WM_xr_actionmap_binding_remove(XrActionMapItem *ami, XrActionMapBinding *amb)
{
int idx = BLI_findindex(&ami->bindings, amb);
if (idx != -1) {
BLI_freelinkN(&ami->bindings, amb);
if (BLI_listbase_is_empty(&ami->bindings)) {
ami->selbinding = -1;
}
else {
if (idx <= ami->selbinding) {
if (--ami->selbinding < 0) {
ami->selbinding = 0;
}
}
}
return true;
}
return false;
}
XrActionMapBinding *WM_xr_actionmap_binding_find(XrActionMapItem *ami, const char *name)
{
LISTBASE_FOREACH (XrActionMapBinding *, amb, &ami->bindings) {
if (STREQLEN(name, amb->name, MAX_NAME)) {
return amb;
}
}
return NULL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Action Map Item
*
* Item in an XR action map, that maps an XR event to an operator, pose, or haptic output.
* \{ */
static void wm_xr_actionmap_item_properties_set(XrActionMapItem *ami)
{
WM_operator_properties_alloc(&(ami->op_properties_ptr), &(ami->op_properties), ami->op);
WM_operator_properties_sanitize(ami->op_properties_ptr, 1);
}
static void wm_xr_actionmap_item_properties_free(XrActionMapItem *ami)
{
if (ami->op_properties_ptr) {
WM_operator_properties_free(ami->op_properties_ptr);
MEM_freeN(ami->op_properties_ptr);
ami->op_properties_ptr = NULL;
ami->op_properties = NULL;
}
else {
BLI_assert(ami->op_properties == NULL);
}
}
/**
* Similar to #wm_xr_actionmap_item_properties_set()
* but checks for the #eXrActionType and #wmOperatorType having changed.
*/
void WM_xr_actionmap_item_properties_update_ot(XrActionMapItem *ami)
{
switch (ami->type) {
case XR_BOOLEAN_INPUT:
case XR_FLOAT_INPUT:
case XR_VECTOR2F_INPUT:
break;
case XR_POSE_INPUT:
case XR_VIBRATION_OUTPUT:
wm_xr_actionmap_item_properties_free(ami);
memset(ami->op, 0, sizeof(ami->op));
return;
}
if (ami->op[0] == 0) {
wm_xr_actionmap_item_properties_free(ami);
return;
}
if (ami->op_properties_ptr == NULL) {
wm_xr_actionmap_item_properties_set(ami);
}
else {
wmOperatorType *ot = WM_operatortype_find(ami->op, 0);
if (ot) {
if (ot->srna != ami->op_properties_ptr->type) {
/* Matches wm_xr_actionmap_item_properties_set() but doesn't alloc new ptr. */
WM_operator_properties_create_ptr(ami->op_properties_ptr, ot);
if (ami->op_properties) {
ami->op_properties_ptr->data = ami->op_properties;
}
WM_operator_properties_sanitize(ami->op_properties_ptr, 1);
}
}
else {
wm_xr_actionmap_item_properties_free(ami);
}
}
}
XrActionMapItem *WM_xr_actionmap_item_new(XrActionMap *actionmap,
const char *name,
bool replace_existing)
{
XrActionMapItem *ami_prev = WM_xr_actionmap_item_find(actionmap, name);
if (ami_prev && replace_existing) {
wm_xr_actionmap_item_properties_free(ami_prev);
return ami_prev;
}
XrActionMapItem *ami = MEM_callocN(sizeof(XrActionMapItem), __func__);
BLI_strncpy(ami->name, name, MAX_NAME);
if (ami_prev) {
WM_xr_actionmap_item_ensure_unique(actionmap, ami);
}
BLI_addtail(&actionmap->items, ami);
/* Set type to float (button) input by default. */
ami->type = XR_FLOAT_INPUT;
return ami;
}
static XrActionMapItem *wm_xr_actionmap_item_find_except(XrActionMap *actionmap,
const char *name,
const XrActionMapItem *amiexcept)
{
LISTBASE_FOREACH (XrActionMapItem *, ami, &actionmap->items) {
if (STREQLEN(name, ami->name, MAX_NAME) && (ami != amiexcept)) {
return ami;
}
}
return NULL;
}
/**
* Ensure unique name among all action map items.
*/
void WM_xr_actionmap_item_ensure_unique(XrActionMap *actionmap, XrActionMapItem *ami)
{
char name[MAX_NAME];
char *suffix;
size_t baselen;
size_t idx = 0;
BLI_strncpy(name, ami->name, MAX_NAME);
baselen = BLI_strnlen(name, MAX_NAME);
suffix = &name[baselen];
while (wm_xr_actionmap_item_find_except(actionmap, name, ami)) {
if ((baselen + 1) + (log10(++idx) + 1) > MAX_NAME) {
/* Use default base name. */
BLI_strncpy(name, WM_XR_ACTIONMAP_ITEM_STR_DEFAULT, MAX_NAME);
baselen = BLI_strnlen(name, MAX_NAME);
suffix = &name[baselen];
idx = 0;
}
else {
BLI_snprintf(suffix, MAX_NAME, "%zu", idx);
}
}
BLI_strncpy(ami->name, name, MAX_NAME);
}
static XrActionMapItem *wm_xr_actionmap_item_copy(XrActionMapItem *ami)
{
XrActionMapItem *amin = MEM_dupallocN(ami);
amin->prev = amin->next = NULL;
if (amin->op_properties) {
amin->op_properties_ptr = MEM_callocN(sizeof(PointerRNA), "wmOpItemPtr");
WM_operator_properties_create(amin->op_properties_ptr, amin->op);
amin->op_properties = IDP_CopyProperty(amin->op_properties);
amin->op_properties_ptr->data = amin->op_properties;
}
else {
amin->op_properties = NULL;
amin->op_properties_ptr = NULL;
}
return amin;
}
XrActionMapItem *WM_xr_actionmap_item_add_copy(XrActionMap *actionmap, XrActionMapItem *ami_src)
{
XrActionMapItem *ami_dst = wm_xr_actionmap_item_copy(ami_src);
WM_xr_actionmap_item_ensure_unique(actionmap, ami_dst);
BLI_addtail(&actionmap->items, ami_dst);
return ami_dst;
}
bool WM_xr_actionmap_item_remove(XrActionMap *actionmap, XrActionMapItem *ami)
{
int idx = BLI_findindex(&actionmap->items, ami);
if (idx != -1) {
if (ami->op_properties_ptr) {
WM_operator_properties_free(ami->op_properties_ptr);
MEM_freeN(ami->op_properties_ptr);
}
BLI_freelinkN(&actionmap->items, ami);
if (BLI_listbase_is_empty(&actionmap->items)) {
actionmap->selitem = -1;
}
else {
if (idx <= actionmap->selitem) {
if (--actionmap->selitem < 0) {
actionmap->selitem = 0;
}
}
}
return true;
}
return false;
}
XrActionMapItem *WM_xr_actionmap_item_find(XrActionMap *actionmap, const char *name)
{
LISTBASE_FOREACH (XrActionMapItem *, ami, &actionmap->items) {
if (STREQLEN(name, ami->name, MAX_NAME)) {
return ami;
}
}
return NULL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Action Map
*
* List of XR action map items.
* \{ */
XrActionMap *WM_xr_actionmap_new(wmXrRuntimeData *runtime, const char *name, bool replace_existing)
{
XrActionMap *am_prev = WM_xr_actionmap_find(runtime, name);
if (am_prev && replace_existing) {
WM_xr_actionmap_clear(am_prev);
return am_prev;
}
XrActionMap *am = MEM_callocN(sizeof(struct XrActionMap), __func__);
BLI_strncpy(am->name, name, MAX_NAME);
if (am_prev) {
WM_xr_actionmap_ensure_unique(runtime, am);
}
BLI_addtail(&runtime->actionmaps, am);
return am;
}
static XrActionMap *wm_xr_actionmap_find_except(wmXrRuntimeData *runtime,
const char *name,
const XrActionMap *am_except)
{
LISTBASE_FOREACH (XrActionMap *, am, &runtime->actionmaps) {
if (STREQLEN(name, am->name, MAX_NAME) && (am != am_except)) {
return am;
}
}
return NULL;
}
/**
* Ensure unique name among all action maps.
*/
void WM_xr_actionmap_ensure_unique(wmXrRuntimeData *runtime, XrActionMap *actionmap)
{
char name[MAX_NAME];
char *suffix;
size_t baselen;
size_t idx = 0;
BLI_strncpy(name, actionmap->name, MAX_NAME);
baselen = BLI_strnlen(name, MAX_NAME);
suffix = &name[baselen];
while (wm_xr_actionmap_find_except(runtime, name, actionmap)) {
if ((baselen + 1) + (log10(++idx) + 1) > MAX_NAME) {
/* Use default base name. */
BLI_strncpy(name, WM_XR_ACTIONMAP_STR_DEFAULT, MAX_NAME);
baselen = BLI_strnlen(name, MAX_NAME);
suffix = &name[baselen];
idx = 0;
}
else {
BLI_snprintf(suffix, MAX_NAME, "%zu", idx);
}
}
BLI_strncpy(actionmap->name, name, MAX_NAME);
}
static XrActionMap *wm_xr_actionmap_copy(XrActionMap *am_src)
{
XrActionMap *am_dst = MEM_dupallocN(am_src);
am_dst->prev = am_dst->next = NULL;
BLI_listbase_clear(&am_dst->items);
LISTBASE_FOREACH (XrActionMapItem *, ami, &am_src->items) {
XrActionMapItem *ami_new = wm_xr_actionmap_item_copy(ami);
BLI_addtail(&am_dst->items, ami_new);
}
return am_dst;
}
XrActionMap *WM_xr_actionmap_add_copy(wmXrRuntimeData *runtime, XrActionMap *am_src)
{
XrActionMap *am_dst = wm_xr_actionmap_copy(am_src);
WM_xr_actionmap_ensure_unique(runtime, am_dst);
BLI_addtail(&runtime->actionmaps, am_dst);
return am_dst;
}
bool WM_xr_actionmap_remove(wmXrRuntimeData *runtime, XrActionMap *actionmap)
{
int idx = BLI_findindex(&runtime->actionmaps, actionmap);
if (idx != -1) {
WM_xr_actionmap_clear(actionmap);
BLI_freelinkN(&runtime->actionmaps, actionmap);
if (BLI_listbase_is_empty(&runtime->actionmaps)) {
runtime->actactionmap = runtime->selactionmap = -1;
}
else {
if (idx <= runtime->actactionmap) {
if (--runtime->actactionmap < 0) {
runtime->actactionmap = 0;
}
}
if (idx <= runtime->selactionmap) {
if (--runtime->selactionmap < 0) {
runtime->selactionmap = 0;
}
}
}
return true;
}
return false;
}
XrActionMap *WM_xr_actionmap_find(wmXrRuntimeData *runtime, const char *name)
{
LISTBASE_FOREACH (XrActionMap *, am, &runtime->actionmaps) {
if (STREQLEN(name, am->name, MAX_NAME)) {
return am;
}
}
return NULL;
}
void WM_xr_actionmap_clear(XrActionMap *actionmap)
{
LISTBASE_FOREACH (XrActionMapItem *, ami, &actionmap->items) {
wm_xr_actionmap_item_properties_free(ami);
}
BLI_freelistN(&actionmap->items);
actionmap->selitem = -1;
}
void WM_xr_actionmaps_clear(wmXrRuntimeData *runtime)
{
LISTBASE_FOREACH (XrActionMap *, am, &runtime->actionmaps) {
WM_xr_actionmap_clear(am);
}
BLI_freelistN(&runtime->actionmaps);
runtime->actactionmap = runtime->selactionmap = -1;
}
ListBase *WM_xr_actionmaps_get(wmXrRuntimeData *runtime)
{
return &runtime->actionmaps;
}
short WM_xr_actionmap_active_index_get(const wmXrRuntimeData *runtime)
{
return runtime->actactionmap;
}
void WM_xr_actionmap_active_index_set(wmXrRuntimeData *runtime, short idx)
{
BLI_assert(idx < BLI_listbase_count(&runtime->actionmaps));
runtime->actactionmap = idx;
}
short WM_xr_actionmap_selected_index_get(const wmXrRuntimeData *runtime)
{
return runtime->selactionmap;
}
void WM_xr_actionmap_selected_index_set(wmXrRuntimeData *runtime, short idx)
{
BLI_assert(idx < BLI_listbase_count(&runtime->actionmaps));
runtime->selactionmap = idx;
}
/** \} */

View File

@ -70,6 +70,10 @@ typedef struct wmXrRuntimeData {
/** Although this struct is internal, RNA gets a handle to this for state information queries. */
wmXrSessionState session_state;
wmXrSessionExitFn exit_fn;
ListBase actionmaps; /* XrActionMap */
short actactionmap;
short selactionmap;
} wmXrRuntimeData;
typedef struct wmXrViewportPair {
@ -99,6 +103,22 @@ typedef struct wmXrDrawData {
float eye_position_ofs[3]; /* Local/view space. */
} wmXrDrawData;
typedef struct wmXrController {
struct wmXrController *next, *prev;
/** OpenXR path identifier. Length is dependent on OpenXR's XR_MAX_PATH_LENGTH (256).
This subaction path will later be combined with a component path, and that combined path should
also have a max of XR_MAX_PATH_LENGTH (e.g. subaction_path = /user/hand/left, component_path =
/input/trigger/value, interaction_path = /user/hand/left/input/trigger/value).
*/
char subaction_path[64];
/* Pose (in world space) that represents the user's hand when holding the controller.*/
GHOST_XrPose grip_pose;
float grip_mat[4][4];
/* Pose (in world space) that represents the controller's aiming source. */
GHOST_XrPose aim_pose;
float aim_mat[4][4];
} wmXrController;
typedef struct wmXrAction {
char *name;
eXrActionType type;
@ -119,32 +139,37 @@ typedef struct wmXrAction {
/** Operator to be called on XR events. */
struct wmOperatorType *ot;
IDProperty *op_properties;
/** Haptics. */
char *haptic_name;
int64_t haptic_duration;
float haptic_frequency;
float haptic_amplitude;
/** Flags. */
eXrOpFlag op_flag;
eXrActionFlag action_flag;
eXrHapticFlag haptic_flag;
} wmXrAction;
typedef struct wmXrController {
struct wmXrController *next, *prev;
/** OpenXR path identifier. Length is dependent on OpenXR's XR_MAX_PATH_LENGTH (256).
This subaction path will later be combined with a component path, and that combined path should
also have a max of XR_MAX_PATH_LENGTH (e.g. subaction_path = /user/hand/left, component_path =
/input/trigger/value, interaction_path = /user/hand/left/input/trigger/value).
*/
char subaction_path[64];
/* Pose (in world space) that represents the user's hand when holding the controller.*/
GHOST_XrPose grip_pose;
float grip_mat[4][4];
/* Pose (in world space) that represents the controller's aiming source. */
GHOST_XrPose aim_pose;
float aim_mat[4][4];
} wmXrController;
typedef struct wmXrHapticAction {
struct wmXrHapticAction *next, *prev;
wmXrAction *action;
const char **subaction_path;
int64_t time_start;
} wmXrHapticAction;
typedef struct wmXrActionSet {
char *name;
/** XR pose actions that determine the controller grip/aim transforms. */
wmXrAction *controller_grip_action;
wmXrAction *controller_aim_action;
/** The currently active modal action (if any). */
wmXrAction *active_modal_action;
/** Currently active modal actions. */
ListBase active_modal_actions;
/** Currently active haptic actions. */
ListBase active_haptic_actions;
} wmXrActionSet;
wmXrRuntimeData *wm_xr_runtime_data_create(void);