XR: Add mouse event simulation

Updated version of https://developer.blender.org/D13153.

This enables practical use of more Blender operators in VR by
converting 3D controller positions to 2D mouse positions as well as
simulating the event type (LEFTMOUSE, MOUSEMOVE...) and value
(PRESS, CLICK...) based on VR controller inputs. In this way, the
view3d.select operator can be used in VR without any modifications to
the operator logic, enabling selection of armatures, empties, etc. in
VR.

If an XR action's simulate_mouse property is set, the 3D position of
the corresponding controller aim pose will be projected to a 2D mouse
position using the perspective of the user's preferred eye
(XrSessionSettings.projection_eye) and the result stored in
wmEvent.xy/mval. In this way, operators can make use of these mouse
values as usual, without needing to know that the event is an XR event.

Along with mouse coordinate simulation, the XR event type and value
can be simulated as a different wmEvent type/value during handling,
where this mapping is configurable via the XrActionMapItem.simulate
(XrSimulateMouseParams) property of an XR action map item. The
simulated type/value can be individually set for press, hold (modal
operators only), and release VR button interactions, depending on the
operator execution mode (XrActionMapItem.op_mode).
This commit is contained in:
Peter Kim 2022-12-12 21:26:01 +09:00
parent 786aad68b2
commit d1b2ed706c
13 changed files with 470 additions and 89 deletions

View File

@ -1346,7 +1346,7 @@ void ED_view3d_local_collections_reset(struct bContext *C, bool reset_all);
#ifdef WITH_XR_OPENXR
void ED_view3d_xr_mirror_update(const struct ScrArea *area, const struct View3D *v3d, bool enable);
void ED_view3d_xr_shading_update(struct wmWindowManager *wm,
const View3D *v3d,
const struct View3D *v3d,
const struct Scene *scene);
bool ED_view3d_is_region_xr_mirror_active(const struct wmWindowManager *wm,
const struct View3D *v3d,

View File

@ -30,7 +30,9 @@ typedef struct XrSessionSettings {
char draw_flags;
/** Draw style for controller visualization. */
char controller_draw_style;
char _pad2[2];
/** The eye (view) used for 3D->2D projection when simulating mouse. */
char projection_eye;
char _pad2;
/** Clipping distance. */
float clip_start, clip_end;
@ -71,6 +73,11 @@ typedef enum eXrSessionControllerDrawStyle {
XR_CONTROLLER_DRAW_LIGHT_RAY = 3,
} eXrSessionControllerDrawStyle;
typedef enum eXrSessionEye {
XR_EYE_LEFT = 0,
XR_EYE_RIGHT = 1,
} eXrSessionEye;
/** XR action type. Enum values match those in GHOST_XrActionType enum for consistency. */
typedef enum eXrActionType {
XR_BOOLEAN_INPUT = 1,
@ -90,6 +97,8 @@ typedef enum eXrOpFlag {
typedef enum eXrActionFlag {
/** Action depends on two sub-action paths (i.e. two-handed/bi-manual action). */
XR_ACTION_BIMANUAL = (1 << 0),
/** Whether to use controller inputs to simulate mouse inputs. */
XR_ACTION_SIMULATE_MOUSE = (1 << 1),
} eXrActionFlag;
typedef enum eXrHapticFlag {
@ -174,6 +183,16 @@ typedef struct XrUserPath {
char path[64]; /* XR_MAX_USER_PATH_LENGTH */
} XrUserPath;
typedef struct XrSimulateMouseParams {
short press_type; /* wmEvent.type */
short press_val; /* wmEvent.val */
short hold_type;
short hold_val;
short release_type;
short release_val;
short _pad[2];
} XrSimulateMouseParams;
typedef struct XrActionMapItem {
struct XrActionMapItem *next, *prev;
@ -193,6 +212,9 @@ typedef struct XrActionMapItem {
/** RNA pointer to access properties. */
struct PointerRNA *op_properties_ptr;
/** Mouse simulation. */
XrSimulateMouseParams simulate;
short op_flag; /* eXrOpFlag */
short action_flag; /* eXrActionFlag */
short haptic_flag; /* eXrHapticFlag */

View File

@ -2832,6 +2832,53 @@ static void rna_def_keyconfig(BlenderRNA *brna)
RNA_api_keymapitem(srna);
}
# ifdef WITH_XR_OPENXR
/* Defined here instead of rna_xr.c to access event type/value enums. */
static void rna_def_xr_simulate_mouse_params(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "XrSimulateMouseParams", NULL);
RNA_def_struct_ui_text(srna,
"XR Mouse Simulation Parameters",
"Parameters for simulating mouse with VR controllers");
prop = RNA_def_property(srna, "press_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "press_type");
RNA_def_property_enum_items(prop, rna_enum_event_type_items);
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_UI_EVENTS);
RNA_def_property_ui_text(prop, "Press Type", "Event type to simulate on VR action press");
prop = RNA_def_property(srna, "press_value", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "press_val");
RNA_def_property_enum_items(prop, rna_enum_event_value_items);
RNA_def_property_ui_text(prop, "Press Value", "Event value to simulate on VR action press");
prop = RNA_def_property(srna, "hold_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "hold_type");
RNA_def_property_enum_items(prop, rna_enum_event_type_items);
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_UI_EVENTS);
RNA_def_property_ui_text(prop, "Hold Type", "Event type to simulate on VR action hold");
prop = RNA_def_property(srna, "hold_value", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "hold_val");
RNA_def_property_enum_items(prop, rna_enum_event_value_items);
RNA_def_property_ui_text(prop, "Hold Value", "Event value to simulate on VR action hold");
prop = RNA_def_property(srna, "release_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "release_type");
RNA_def_property_enum_items(prop, rna_enum_event_type_items);
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_UI_EVENTS);
RNA_def_property_ui_text(prop, "Release Type", "Event type to simulate on VR action release");
prop = RNA_def_property(srna, "release_value", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "release_val");
RNA_def_property_enum_items(prop, rna_enum_event_value_items);
RNA_def_property_ui_text(prop, "Release Value", "Event value to simulate on VR action release");
}
# endif
void RNA_def_wm(BlenderRNA *brna)
{
rna_def_operator(brna);
@ -2849,6 +2896,9 @@ void RNA_def_wm(BlenderRNA *brna)
rna_def_windowmanager(brna);
rna_def_keyconfig_prefs(brna);
rna_def_keyconfig(brna);
# ifdef WITH_XR_OPENXR
rna_def_xr_simulate_mouse_params(brna);
# endif
}
#endif /* RNA_RUNTIME */

View File

@ -426,6 +426,41 @@ static void rna_XrActionMapItem_bimanual_set(PointerRNA *ptr, bool value)
# endif
}
static bool rna_XrActionMapItem_simulate_mouse_get(PointerRNA *ptr)
{
# ifdef WITH_XR_OPENXR
XrActionMapItem *ami = ptr->data;
if ((ami->action_flag & XR_ACTION_SIMULATE_MOUSE) != 0) {
return true;
}
# else
UNUSED_VARS(ptr);
# endif
return false;
}
static void rna_XrActionMapItem_simulate_mouse_set(PointerRNA *ptr, bool value)
{
# ifdef WITH_XR_OPENXR
XrActionMapItem *ami = ptr->data;
SET_FLAG_FROM_TEST(ami->action_flag, value, XR_ACTION_SIMULATE_MOUSE);
# else
UNUSED_VARS(ptr, value);
# endif
}
static PointerRNA rna_XrActionMapItem_simulate_get(PointerRNA *ptr)
{
# ifdef WITH_XR_OPENXR
XrActionMapItem *ami = ptr->data;
XrSimulateMouseParams *simulate = &ami->simulate;
return rna_pointer_inherit_refine(ptr, &RNA_XrSimulateMouseParams, simulate);
# else
UNUSED_VARS(ptr);
return PointerRNA_NULL;
# endif
}
static bool rna_XrActionMapItem_haptic_match_user_paths_get(PointerRNA *ptr)
{
# ifdef WITH_XR_OPENXR
@ -1003,6 +1038,7 @@ static bool rna_XrSessionState_action_create(bContext *C,
&ami->user_paths,
ot,
op_properties,
is_button_action ? &ami->simulate : NULL,
is_button_action ? ami->haptic_name : NULL,
is_button_action ? &haptic_duration_msec : NULL,
is_button_action ? &ami->haptic_frequency : NULL,
@ -1545,6 +1581,17 @@ static bool rna_XrEventData_bimanual_get(PointerRNA *ptr)
# endif
}
static bool rna_XrEventData_simulate_mouse_get(PointerRNA *ptr)
{
# ifdef WITH_XR_OPENXR
const wmXrActionData *data = ptr->data;
return data->simulate_mouse;
# else
UNUSED_VARS(ptr);
return false;
# endif
}
/** \} */
#else /* RNA_RUNTIME */
@ -1951,6 +1998,18 @@ static void rna_def_xr_actionmap(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "Bimanual", "The action depends on the states/poses of both user paths");
prop = RNA_def_property(srna, "simulate_mouse", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(
prop, "rna_XrActionMapItem_simulate_mouse_get", "rna_XrActionMapItem_simulate_mouse_set");
RNA_def_property_ui_text(
prop, "Simulate Mouse", "Use VR controller inputs to simulate mouse inputs");
prop = RNA_def_property(srna, "simulate", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "XrSimulateMouseParams");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_pointer_funcs(prop, "rna_XrActionMapItem_simulate_get", NULL, NULL, NULL);
RNA_def_property_ui_text(prop, "Simulate", "Mouse simulation parameters");
prop = RNA_def_property(srna, "pose_is_controller_grip", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(prop,
"rna_XrActionMapItem_pose_is_controller_grip_get",
@ -2234,6 +2293,20 @@ static void rna_def_xr_session_settings(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem projection_eyes[] = {
{XR_EYE_LEFT,
"EYE_LEFT",
0,
"Left Eye",
"Use the left eye's perspective for projection when simulating mouse"},
{XR_EYE_RIGHT,
"EYE_RIGHT",
0,
"Right Eye",
"Use the right eye's perspective for projection when simulating mouse"},
{0, NULL, 0, NULL, NULL},
};
srna = RNA_def_struct(brna, "XrSessionSettings", NULL);
RNA_def_struct_ui_text(srna, "XR Session Settings", "");
@ -2320,6 +2393,14 @@ static void rna_def_xr_session_settings(BlenderRNA *brna)
prop, "Controller Draw Style", "Style to use when drawing VR controllers");
RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
prop = RNA_def_property(srna, "projection_eye", PROP_ENUM, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_enum_items(prop, projection_eyes);
RNA_def_property_ui_text(prop,
"Projection Eye",
"Which eye's perspective to use for projection when simulating mouse");
RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
prop = RNA_def_property(srna, "clip_start", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_range(prop, 1e-6f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.001f, FLT_MAX, 10, 3);
@ -2835,7 +2916,7 @@ static void rna_def_xr_eventdata(BlenderRNA *brna)
PropertyRNA *prop;
srna = RNA_def_struct(brna, "XrEventData", NULL);
RNA_def_struct_ui_text(srna, "XrEventData", "XR Data for Window Manager Event");
RNA_def_struct_ui_text(srna, "Xr Event Data", "XR Data for Window Manager Event");
prop = RNA_def_property(srna, "action_set", PROP_STRING, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
@ -2922,6 +3003,12 @@ static void rna_def_xr_eventdata(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_boolean_funcs(prop, "rna_XrEventData_bimanual_get", NULL);
RNA_def_property_ui_text(prop, "Bimanual", "Whether bimanual interaction is occurring");
prop = RNA_def_property(srna, "simulate_mouse", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_boolean_funcs(prop, "rna_XrEventData_simulate_mouse_get", NULL);
RNA_def_property_ui_text(
prop, "Simulate Mouse", "Whether the action is simulating mouse inputs");
}
/** \} */

View File

@ -1734,6 +1734,7 @@ bool WM_xr_action_create(wmXrData *xr,
const ListBase *user_paths,
struct wmOperatorType *ot,
struct IDProperty *op_properties,
const XrSimulateMouseParams *simulate,
const char *haptic_name,
const int64_t *haptic_duration,
const float *haptic_frequency,

View File

@ -841,6 +841,11 @@ typedef struct wmXrActionData {
/** Whether bimanual interaction is occurring. */
bool bimanual;
/** Whether the action is simulating mouse inputs. */
bool simulate_mouse;
/** Type and value for simulated mouse event. */
short simulate_type;
short simulate_val;
} wmXrActionData;
#endif

View File

@ -936,11 +936,16 @@ void wm_gizmomap_handler_context_op(bContext *C, wmEventHandler_Op *handler)
bScreen *screen = CTX_wm_screen(C);
if (screen) {
ScrArea *area;
ScrArea *area = NULL;
for (area = screen->areabase.first; area; area = area->next) {
if (area == handler->context.area) {
break;
if ((handler->context.area->flag & AREA_FLAG_OFFSCREEN) != 0) {
area = handler->context.area;
}
else {
for (area = screen->areabase.first; area; area = area->next) {
if (area == handler->context.area) {
break;
}
}
}
if (area == NULL) {

View File

@ -142,14 +142,8 @@ wmEvent *wm_event_add(wmWindow *win, const wmEvent *event_to_add)
return wm_event_add_ex(win, event_to_add, nullptr);
}
wmEvent *WM_event_add_simulate(wmWindow *win, const wmEvent *event_to_add)
static void wm_event_simulate_eventstate(wmWindow *win, wmEvent *event)
{
if ((G.f & G_FLAG_EVENT_SIMULATE) == 0) {
BLI_assert_unreachable();
return nullptr;
}
wmEvent *event = wm_event_add(win, event_to_add);
/* Logic for setting previous value is documented on the #wmEvent struct,
* see #wm_event_add_ghostevent for the implementation of logic this follows. */
copy_v2_v2_int(win->eventstate->xy, event->xy);
@ -158,9 +152,21 @@ wmEvent *WM_event_add_simulate(wmWindow *win, const wmEvent *event_to_add)
copy_v2_v2_int(win->eventstate->prev_xy, win->eventstate->xy);
copy_v2_v2_int(event->prev_xy, win->eventstate->xy);
}
else if (ISKEYBOARD_OR_BUTTON(event->type)) {
else if (ISKEYBOARD_OR_BUTTON(event->type) && ELEM(event->val, KM_PRESS, KM_RELEASE)) {
wm_event_state_update_and_click_set_ex(event, win->eventstate, ISKEYBOARD(event->type), false);
}
}
wmEvent *WM_event_add_simulate(wmWindow *win, const wmEvent *event_to_add)
{
if ((G.f & G_FLAG_EVENT_SIMULATE) == 0) {
BLI_assert_unreachable();
return nullptr;
}
wmEvent *event = wm_event_add(win, event_to_add);
wm_event_simulate_eventstate(win, event);
return event;
}
@ -1990,10 +1996,15 @@ static void wm_handler_op_context_get_if_valid(bContext *C,
else {
ScrArea *area = nullptr;
ED_screen_areas_iter (win, screen, area_iter) {
if (area_iter == handler->context.area) {
area = area_iter;
break;
if ((handler->context.area->flag & AREA_FLAG_OFFSCREEN) != 0) {
area = handler->context.area;
}
else {
ED_screen_areas_iter (win, screen, area_iter) {
if (area_iter == handler->context.area) {
area = area_iter;
break;
}
}
}
@ -3698,13 +3709,22 @@ static void wm_event_handle_xrevent(bContext *C,
}
BLI_assert(WM_region_use_viewport(area, region)); /* For operators using GPU-based selection. */
/* Check for simulated mouse event. */
wmXrActionData *actiondata = static_cast<wmXrActionData *>(event->customdata);
const bool simulate_event = (actiondata->simulate_mouse &&
(actiondata->simulate_type != EVENT_NONE));
if (simulate_event) {
event->type = actiondata->simulate_type;
event->val = actiondata->simulate_val;
wm_event_simulate_eventstate(win, event);
}
CTX_wm_area_set(C, area);
CTX_wm_region_set(C, region);
int action = wm_handlers_do(C, event, &win->modalhandlers);
if ((action & WM_HANDLER_BREAK) == 0) {
wmXrActionData *actiondata = static_cast<wmXrActionData *>(event->customdata);
if (actiondata->ot->modal && event->val == KM_RELEASE) {
/* Don't execute modal operators on release. */
}
@ -5693,7 +5713,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void
}
#ifdef WITH_XR_OPENXR
void wm_event_add_xrevent(wmWindow *win, wmXrActionData *actiondata, short val)
void wm_event_add_xrevent(wmWindow *win, wmXrActionData *actiondata, short val, int mval[2])
{
BLI_assert(ELEM(val, KM_PRESS, KM_RELEASE));
@ -5705,6 +5725,11 @@ void wm_event_add_xrevent(wmWindow *win, wmXrActionData *actiondata, short val)
event.customdata = actiondata;
event.customdata_free = true;
if (mval) {
copy_v2_v2_int(event.xy, mval);
copy_v2_v2_int(event.mval, mval);
}
wm_event_add(win, &event);
}
#endif /* WITH_XR_OPENXR */

View File

@ -145,7 +145,10 @@ void wm_event_do_handlers(bContext *C);
*/
void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void *customdata);
#ifdef WITH_XR_OPENXR
void wm_event_add_xrevent(wmWindow *win, struct wmXrActionData *actiondata, short val);
void wm_event_add_xrevent(wmWindow *win,
struct wmXrActionData *actiondata,
short val,
int mval[2]);
#endif
void wm_event_do_depsgraph(bContext *C, bool is_after_open_file);

View File

@ -60,6 +60,7 @@ static wmXrAction *action_create(const char *action_name,
const ListBase *user_paths,
wmOperatorType *ot,
IDProperty *op_properties,
const XrSimulateMouseParams *simulate,
const char *haptic_name,
const int64_t *haptic_duration,
const float *haptic_frequency,
@ -119,6 +120,11 @@ static wmXrAction *action_create(const char *action_name,
action->ot = ot;
action->op_properties = op_properties;
if (simulate) {
BLI_assert(is_button_action);
memcpy(&action->simulate, simulate, sizeof(action->simulate));
}
if (haptic_name) {
BLI_assert(is_button_action);
action->haptic_name = MEM_mallocN(strlen(haptic_name) + 1, "XrAction_HapticName");
@ -222,6 +228,7 @@ bool WM_xr_action_create(wmXrData *xr,
const ListBase *user_paths,
wmOperatorType *ot,
IDProperty *op_properties,
const XrSimulateMouseParams *simulate,
const char *haptic_name,
const int64_t *haptic_duration,
const float *haptic_frequency,
@ -239,6 +246,7 @@ bool WM_xr_action_create(wmXrData *xr,
user_paths,
ot,
op_properties,
simulate,
haptic_name,
haptic_duration,
haptic_frequency,

View File

@ -73,10 +73,11 @@ static void wm_xr_draw_matrices_create(const wmXrDrawData *draw_data,
const XrSessionSettings *session_settings,
const wmXrSessionState *session_state,
float r_viewmat[4][4],
float r_viewmat_base[4][4],
float r_projmat[4][4])
{
GHOST_XrPose eye_pose;
float eye_inv[4][4], base_inv[4][4], nav_inv[4][4], m[4][4];
float eye_inv[4][4], base_inv[4][4], nav_inv[4][4];
/* Calculate inverse eye matrix. */
copy_qt_qt(eye_pose.orientation_quat, draw_view->eye_pose.orientation_quat);
@ -93,8 +94,8 @@ static void wm_xr_draw_matrices_create(const wmXrDrawData *draw_data,
/* Apply base pose and navigation. */
wm_xr_pose_scale_to_imat(&draw_data->base_pose, draw_data->base_scale, base_inv);
wm_xr_pose_scale_to_imat(&session_state->nav_pose_prev, session_state->nav_scale_prev, nav_inv);
mul_m4_m4m4(m, eye_inv, base_inv);
mul_m4_m4m4(r_viewmat, m, nav_inv);
mul_m4_m4m4(r_viewmat_base, eye_inv, base_inv);
mul_m4_m4m4(r_viewmat, r_viewmat_base, nav_inv);
perspective_m4_fov(r_projmat,
draw_view->fov.angle_left,
@ -135,13 +136,14 @@ void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata)
const int display_flags = V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS | settings->draw_flags;
float viewmat[4][4], winmat[4][4];
float viewmat[4][4], viewmat_base[4][4], winmat[4][4];
BLI_assert(WM_xr_session_is_ready(xr_data));
wm_xr_session_draw_data_update(session_state, settings, draw_view, draw_data);
wm_xr_draw_matrices_create(draw_data, draw_view, settings, session_state, viewmat, winmat);
wm_xr_session_state_update(settings, draw_data, draw_view, session_state);
wm_xr_draw_matrices_create(
draw_data, draw_view, settings, session_state, viewmat, viewmat_base, winmat);
wm_xr_session_state_update(settings, draw_data, draw_view, viewmat, viewmat_base, session_state);
if (!wm_xr_session_surface_offscreen_ensure(surface_data, draw_view)) {
return;

View File

@ -21,7 +21,6 @@ typedef struct wmXrSessionState {
float viewer_viewmat[4][4];
/** The last known viewer matrix, without navigation applied. */
float viewer_mat_base[4][4];
float focal_len;
/** Copy of XrSessionSettings.base_pose_ data to detect changes that need
* resetting to base pose. */
@ -50,6 +49,8 @@ typedef struct wmXrSessionState {
float nav_scale_prev;
bool is_navigation_dirty;
/** Last known eye data. */
ListBase eyes; /* #wmXrEye */
/** Last known controller data. */
ListBase controllers; /* #wmXrController */
/** Last known tracker data. */
@ -118,6 +119,14 @@ typedef struct wmXrDrawData {
float eye_position_ofs[3]; /* Local/view space. */
} wmXrDrawData;
typedef struct wmXrEye {
struct wmXrEye *next, *prev;
float focal_len;
float viewmat[4][4];
/** Viewmat without navigation applied. */
float viewmat_base[4][4];
} wmXrEye;
typedef struct wmXrController {
struct wmXrController *next, *prev;
/** OpenXR user path identifier. */
@ -157,6 +166,9 @@ typedef struct wmXrAction {
struct wmOperatorType *ot;
IDProperty *op_properties;
/** Mouse simulation. */
XrSimulateMouseParams simulate;
/** Haptics. */
char *haptic_name;
int64_t haptic_duration;
@ -198,12 +210,10 @@ typedef struct wmXrMotionCapturePose {
} wmXrMotionCapturePose;
/* wm_xr.c */
wmXrRuntimeData *wm_xr_runtime_data_create(void);
void wm_xr_runtime_data_free(wmXrRuntimeData **runtime);
/* wm_xr_session.c */
void wm_xr_session_data_free(wmXrSessionState *state);
wmWindow *wm_xr_session_root_window_or_fallback_get(const wmWindowManager *wm,
const wmXrRuntimeData *runtime_data);
@ -219,6 +229,8 @@ void wm_xr_session_draw_data_update(wmXrSessionState *state,
void wm_xr_session_state_update(const XrSessionSettings *settings,
const wmXrDrawData *draw_data,
const GHOST_XrDrawViewInfo *draw_view,
const float viewmat[4][4],
const float viewmat_base[4][4],
wmXrSessionState *state);
bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data,
const GHOST_XrDrawViewInfo *draw_view);

View File

@ -25,6 +25,7 @@
#include "ED_screen.h"
#include "ED_space_api.h"
#include "ED_view3d.h"
#include "GHOST_C-api.h"
@ -88,6 +89,7 @@ static void wm_xr_session_controller_data_free(ListBase *controllers)
void wm_xr_session_data_free(wmXrSessionState *state)
{
BLI_freelistN(&state->eyes);
wm_xr_session_controller_data_free(&state->controllers);
wm_xr_session_controller_data_free(&state->trackers);
BLI_freelistN(&state->mocap_orig_poses);
@ -344,11 +346,23 @@ void wm_xr_session_draw_data_update(wmXrSessionState *state,
void wm_xr_session_state_update(const XrSessionSettings *settings,
const wmXrDrawData *draw_data,
const GHOST_XrDrawViewInfo *draw_view,
const float viewmat[4][4],
const float viewmat_base[4][4],
wmXrSessionState *state)
{
GHOST_XrPose viewer_pose;
float viewer_mat[4][4], base_mat[4][4], nav_mat[4][4];
wmXrEye *eye = NULL;
if (draw_view->view_idx >= BLI_listbase_count(&state->eyes)) {
eye = MEM_callocN(sizeof(*eye), __func__);
BLI_addtail(&state->eyes, eye);
}
else {
eye = BLI_findlink(&state->eyes, draw_view->view_idx);
}
BLI_assert(eye);
/* Calculate viewer matrix. */
copy_qt_qt(viewer_pose.orientation_quat, draw_view->local_pose.orientation_quat);
if ((settings->flag & XR_SESSION_USE_POSITION_TRACKING) == 0) {
@ -374,9 +388,11 @@ void wm_xr_session_state_update(const XrSessionSettings *settings,
&state->viewer_pose, draw_data->base_scale * state->nav_scale_prev, state->viewer_viewmat);
/* No idea why, but multiplying by two seems to make it match the VR view more. */
state->focal_len = 2.0f *
fov_to_focallength(draw_view->fov.angle_right - draw_view->fov.angle_left,
DEFAULT_SENSOR_WIDTH);
eye->focal_len = 2.0f *
fov_to_focallength(draw_view->fov.angle_right - draw_view->fov.angle_left,
DEFAULT_SENSOR_WIDTH);
copy_m4_m4(eye->viewmat, viewmat);
copy_m4_m4(eye->viewmat_base, viewmat_base);
copy_v3_v3(state->prev_eye_position_ofs, draw_data->eye_position_ofs);
memcpy(&state->prev_base_pose, &draw_data->base_pose, sizeof(state->prev_base_pose));
@ -435,7 +451,16 @@ bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr,
}
copy_m4_m4(r_viewmat, xr->runtime->session_state.viewer_viewmat);
*r_focal_len = xr->runtime->session_state.focal_len;
/* Since viewer (eye centroid) does not have a focal length, just take it from projection eye. */
const wmXrEye *eye = BLI_findlink(&xr->runtime->session_state.eyes,
xr->session_settings.projection_eye);
if (!eye) {
/* Fall back to first XR view. */
eye = xr->runtime->session_state.eyes.first;
BLI_assert(eye);
}
*r_focal_len = eye->focal_len;
return true;
}
@ -954,7 +979,8 @@ static void wm_xr_session_action_states_interpret(wmXrData *xr,
int64_t time_now,
bool modal,
bool haptic,
short *r_val)
short *r_val,
bool *r_press_start)
{
const char *haptic_subaction_path = ((action->haptic_flag & XR_HAPTIC_MATCHUSERPATHS) != 0) ?
action->subaction_paths[subaction_idx] :
@ -1016,6 +1042,7 @@ static void wm_xr_session_action_states_interpret(wmXrData *xr,
if (!prev) {
if (modal || (action->op_flag == XR_OP_PRESS)) {
*r_val = KM_PRESS;
*r_press_start = true;
}
if (haptic && (action->haptic_flag & (XR_HAPTIC_PRESS | XR_HAPTIC_REPEAT)) != 0) {
/* Apply haptics. */
@ -1033,6 +1060,7 @@ static void wm_xr_session_action_states_interpret(wmXrData *xr,
}
else if (modal) {
*r_val = KM_PRESS;
*r_press_start = false;
}
if (modal && !action->active_modal_path) {
/* Set active modal path. */
@ -1060,6 +1088,7 @@ static void wm_xr_session_action_states_interpret(wmXrData *xr,
else if (prev) {
if (modal || (action->op_flag == XR_OP_RELEASE)) {
*r_val = KM_RELEASE;
*r_press_start = false;
if (modal && (action->subaction_paths[subaction_idx] == action->active_modal_path)) {
/* Unset active modal path. */
action->active_modal_path = NULL;
@ -1152,7 +1181,9 @@ static wmXrActionData *wm_xr_session_event_create(const char *action_set_name,
const GHOST_XrPose *controller_aim_pose_other,
uint subaction_idx,
uint subaction_idx_other,
bool bimanual)
bool bimanual,
short val,
bool press_start)
{
wmXrActionData *data = MEM_callocN(sizeof(wmXrActionData), __func__);
strcpy(data->action_set, action_set_name);
@ -1211,10 +1242,56 @@ static wmXrActionData *wm_xr_session_event_create(const char *action_set_name,
data->op_properties = action->op_properties;
data->bimanual = bimanual;
data->simulate_mouse = (((action->action_flag & XR_ACTION_SIMULATE_MOUSE) != 0) &&
controller_aim_pose);
if (data->simulate_mouse) {
switch (val) {
case KM_PRESS:
if (press_start) {
data->simulate_type = action->simulate.press_type;
data->simulate_val = action->simulate.press_val;
}
else {
data->simulate_type = action->simulate.hold_type;
data->simulate_val = action->simulate.hold_val;
}
break;
case KM_RELEASE:
data->simulate_type = action->simulate.release_type;
data->simulate_val = action->simulate.release_val;
break;
default:
BLI_assert_unreachable();
break;
}
}
return data;
}
/**
* Convert 3D controller coordinates to 2D mouse (pixel) coordinates.
*/
static void map_to_pixel(
const float co[3], const float persmat[4][4], int winx, int winy, int r_co[2])
{
float vec[3];
mul_v3_project_m4_v3(vec, persmat, co);
r_co[0] = (int)(((float)winx / 2.0f) * (1.0f + vec[0]));
r_co[1] = (int)(((float)winy / 2.0f) * (1.0f + vec[1]));
}
static void wm_xr_session_event_mval_calc(const wmXrRuntimeData *runtime,
const GHOST_XrPose *controller_aim_pose,
int r_mval[2])
{
const ARegion *region = BKE_area_find_region_type(runtime->area, RGN_TYPE_WINDOW);
BLI_assert(region);
const RegionView3D *rv3d = region->regiondata;
BLI_assert(rv3d);
map_to_pixel(controller_aim_pose->position, rv3d->persmat, region->winx, region->winy, r_mval);
}
/* Dispatch events to window queues. */
static void wm_xr_session_events_dispatch(wmXrData *xr,
GHOST_XrContextHandle xr_context,
@ -1223,19 +1300,16 @@ static void wm_xr_session_events_dispatch(wmXrData *xr,
wmWindow *win)
{
const char *action_set_name = action_set->name;
const uint count = GHOST_XrGetActionCount(xr_context, action_set_name);
if (count < 1) {
return;
}
const int64_t time_now = (int64_t)(PIL_check_seconds_timer() * 1000);
ListBase *active_modal_actions = &action_set->active_modal_actions;
ListBase *active_haptic_actions = &action_set->active_haptic_actions;
wmXrAction **actions = MEM_calloc_arrayN(count, sizeof(*actions), __func__);
GHOST_XrGetActionCustomdataArray(xr_context, action_set_name, (void **)actions);
/* Check haptic action timers. */
@ -1243,60 +1317,145 @@ static void wm_xr_session_events_dispatch(wmXrData *xr,
for (uint action_idx = 0; action_idx < count; ++action_idx) {
wmXrAction *action = actions[action_idx];
if (action && action->ot) {
const bool modal = action->ot->modal;
const bool haptic = (GHOST_XrGetActionCustomdata(
xr_context, action_set_name, action->haptic_name) != NULL);
if (!action || !action->ot) {
continue;
}
for (uint subaction_idx = 0; subaction_idx < action->count_subaction_paths;
++subaction_idx) {
short val = KM_NOTHING;
const bool modal = action->ot->modal;
const bool haptic = (GHOST_XrGetActionCustomdata(
xr_context, action_set_name, action->haptic_name) != NULL);
/* Interpret action states (update modal/haptic action lists, apply haptics, etc). */
wm_xr_session_action_states_interpret(xr,
action_set_name,
action,
subaction_idx,
active_modal_actions,
active_haptic_actions,
time_now,
modal,
haptic,
&val);
for (uint subaction_idx = 0; subaction_idx < action->count_subaction_paths; ++subaction_idx) {
short val = KM_NOTHING;
bool press_start = false;
/* Interpret action states (update modal/haptic action lists, apply haptics, etc). */
wm_xr_session_action_states_interpret(xr,
action_set_name,
action,
subaction_idx,
active_modal_actions,
active_haptic_actions,
time_now,
modal,
haptic,
&val,
&press_start);
if (val == KM_NOTHING) {
continue;
}
if (modal) {
const bool is_active_modal_action = wm_xr_session_modal_action_test(
active_modal_actions, action, NULL);
if (!is_active_modal_action) {
continue;
}
const bool is_active_modal_subaction = (!action->active_modal_path ||
(action->subaction_paths[subaction_idx] ==
action->active_modal_path));
if ((val != KM_NOTHING) &&
(!modal || (is_active_modal_action && is_active_modal_subaction))) {
const GHOST_XrPose *aim_pose = wm_xr_session_controller_aim_pose_find(
session_state, action->subaction_paths[subaction_idx]);
const GHOST_XrPose *aim_pose_other = NULL;
uint subaction_idx_other = 0;
/* Test for bimanual interaction. */
const bool bimanual = wm_xr_session_action_test_bimanual(
session_state, action, subaction_idx, &subaction_idx_other, &aim_pose_other);
wmXrActionData *actiondata = wm_xr_session_event_create(action_set_name,
action,
aim_pose,
aim_pose_other,
subaction_idx,
subaction_idx_other,
bimanual);
wm_event_add_xrevent(win, actiondata, val);
if (!is_active_modal_subaction) {
continue;
}
}
const GHOST_XrPose *aim_pose = wm_xr_session_controller_aim_pose_find(
session_state, action->subaction_paths[subaction_idx]);
const GHOST_XrPose *aim_pose_other = NULL;
unsigned int subaction_idx_other = 0;
int mval[2];
/* Test for bimanual interaction. */
const bool bimanual = wm_xr_session_action_test_bimanual(
session_state, action, subaction_idx, &subaction_idx_other, &aim_pose_other);
wmXrActionData *actiondata = wm_xr_session_event_create(action_set_name,
action,
aim_pose,
aim_pose_other,
subaction_idx,
subaction_idx_other,
bimanual,
val,
press_start);
/* Calculate simulated mouse coordinates. */
if (actiondata->simulate_mouse) {
wm_xr_session_event_mval_calc(xr->runtime, aim_pose, mval);
}
wm_event_add_xrevent(win, actiondata, val, actiondata->simulate_mouse ? mval : NULL);
}
}
MEM_freeN(actions);
}
static bool wm_xr_session_area_ensure(const XrSessionSettings *settings,
const wmXrSessionState *state,
const wmXrSurfaceData *surface_data,
wmWindowManager *wm,
Scene *scene,
Depsgraph *depsgraph,
wmWindow *win)
{
if (settings->projection_eye >= BLI_listbase_count(&state->eyes) ||
settings->projection_eye >= BLI_listbase_count(&surface_data->viewports)) {
return false;
}
const wmXrEye *eye = BLI_findlink(&state->eyes, settings->projection_eye);
BLI_assert(eye);
const wmXrViewportPair *vp = BLI_findlink(&surface_data->viewports, settings->projection_eye);
BLI_assert(vp && vp->offscreen);
const int width = GPU_offscreen_width(vp->offscreen);
const int height = GPU_offscreen_height(vp->offscreen);
wmXrRuntimeData *runtime = wm->xr.runtime;
wmWindow *win_prev = wm->windrawable;
/* Ensure an XR area exists for events. */
if (!runtime->area) {
runtime->area = ED_area_offscreen_create(win, SPACE_VIEW3D);
}
ARegion *region = BKE_area_find_region_type(runtime->area, RGN_TYPE_WINDOW);
BLI_assert(region);
View3D *v3d = (View3D *)runtime->area->spacedata.first;
BLI_assert(v3d);
RegionView3D *rv3d = region->regiondata;
BLI_assert(rv3d);
/* Ensure region dimensions and view params match the XR (projection eye) view.*/
runtime->area->winx = width;
runtime->area->winy = height;
region->winx = width;
region->winy = height;
region->winrct.xmin = 0;
region->winrct.xmax = width - 1;
region->winrct.ymin = 0;
region->winrct.ymax = height - 1;
v3d->clip_start = settings->clip_start;
v3d->clip_end = settings->clip_end;
v3d->object_type_exclude_viewport = settings->object_type_exclude_viewport;
v3d->object_type_exclude_select = settings->object_type_exclude_select;
rv3d->persp = RV3D_PERSP;
wm_window_make_drawable(wm, win);
ED_view3d_update_viewmat(depsgraph, scene, v3d, region, eye->viewmat, NULL, NULL, true);
if (win_prev) {
wm_window_make_drawable(wm, win_prev);
}
else {
wm_window_clear_drawable(wm);
}
return true;
}
void wm_xr_session_actions_update(const bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
@ -1323,6 +1482,11 @@ void wm_xr_session_actions_update(const bContext *C)
mat4_to_loc_quat(state->viewer_pose.position, state->viewer_pose.orientation_quat, viewer_mat);
wm_xr_pose_scale_to_imat(
&state->viewer_pose, settings->base_scale * state->nav_scale, state->viewer_viewmat);
wm_xr_pose_scale_to_imat(&state->nav_pose, state->nav_scale, m);
LISTBASE_FOREACH (wmXrEye *, eye, &state->eyes) {
mul_m4_m4m4(eye->viewmat, eye->viewmat_base, m);
}
}
/* Set active action set if requested previously. */
@ -1369,18 +1533,15 @@ void wm_xr_session_actions_update(const bContext *C)
win);
}
if (win) {
/* Ensure an XR area exists for events. */
if (!xr->runtime->area) {
xr->runtime->area = ED_area_offscreen_create(win, SPACE_VIEW3D);
if (win && g_xr_surface) {
Scene *scene;
Depsgraph *depsgraph;
wm_xr_session_scene_and_depsgraph_get(wm, &scene, &depsgraph);
if (wm_xr_session_area_ensure(
settings, state, g_xr_surface->customdata, wm, scene, depsgraph, win)) {
wm_xr_session_events_dispatch(xr, xr_context, active_action_set, state, win);
}
/* Set XR area object type flags for operators. */
View3D *v3d = xr->runtime->area->spacedata.first;
v3d->object_type_exclude_viewport = settings->object_type_exclude_viewport;
v3d->object_type_exclude_select = settings->object_type_exclude_select;
wm_xr_session_events_dispatch(xr, xr_context, active_action_set, state, win);
}
}
}