diff options
Diffstat (limited to 'source')
56 files changed, 6339 insertions, 454 deletions
diff --git a/source/blender/blenlib/BLI_math_geom.h b/source/blender/blenlib/BLI_math_geom.h index bcda25ca533..d2a19813f5a 100644 --- a/source/blender/blenlib/BLI_math_geom.h +++ b/source/blender/blenlib/BLI_math_geom.h @@ -717,6 +717,12 @@ void map_to_plane_axis_angle_v2_v3v3fl(float r_co[2], const float co[3], const float axis[3], const float angle); +void map_to_pixel(int r_co[2], + const float co[3], + const float viewmat[4][4], + const float winmat[4][4], + int winx, + int winy); /********************************** Normals **********************************/ diff --git a/source/blender/blenlib/intern/math_geom.c b/source/blender/blenlib/intern/math_geom.c index 8afb6b5a2be..64bf9e92270 100644 --- a/source/blender/blenlib/intern/math_geom.c +++ b/source/blender/blenlib/intern/math_geom.c @@ -5277,6 +5277,23 @@ void map_to_plane_axis_angle_v2_v3v3fl(float r_co[2], copy_v2_v2(r_co, tmp); } +void map_to_pixel(int r_co[2], + const float co[3], + const float viewmat[4][4], + const float winmat[4][4], + int winx, + int winy) +{ + float persmat[4][4]; + float vec[3]; + + mul_m4_m4m4(persmat, winmat, viewmat); + 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])); +} + /********************************* Normals **********************************/ void accumulate_vertex_normals_tri_v3(float n1[3], diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 3ee5e41bf1f..cb3e81ba6d5 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -2489,6 +2489,10 @@ static void lib_link_wm_xr_data_restore(struct IDNameLib_Map *id_map, wmXrData * { xr_data->session_settings.base_pose_object = restore_pointer_by_name( id_map, (ID *)xr_data->session_settings.base_pose_object, USER_REAL); + + LISTBASE_FOREACH (XrMotionCaptureObject *, mocap_ob, &xr_data->session_settings.mocap_objects) { + mocap_ob->ob = restore_pointer_by_name(id_map, (ID *)mocap_ob->ob, USER_REAL); + } } static void lib_link_window_scene_data_restore(wmWindow *win, Scene *scene, ViewLayer *view_layer) diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 95fa3058931..323391d9715 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -1189,6 +1189,14 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } + if (!MAIN_VERSION_ATLEAST(bmain, 300, 7)) { + LISTBASE_FOREACH (wmWindowManager *, wm, &bmain->wm) { + wm->xr.session_settings.draw_flags |= (V3D_OFSDRAW_SHOW_SELECTION | + V3D_OFSDRAW_XR_SHOW_CONTROLLERS | + V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS); + } + } + if (!MAIN_VERSION_ATLEAST(bmain, 300, 8)) { LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { if (scene->master_collection != NULL) { diff --git a/source/blender/draw/engines/overlay/overlay_grid.c b/source/blender/draw/engines/overlay/overlay_grid.c index 31c8ed9d664..cc8ab00baef 100644 --- a/source/blender/draw/engines/overlay/overlay_grid.c +++ b/source/blender/draw/engines/overlay/overlay_grid.c @@ -200,6 +200,15 @@ void OVERLAY_grid_init(OVERLAY_Data *vedata) shd->grid_distance = dist / 2.0f; ED_view3d_grid_steps(scene, v3d, rv3d, shd->grid_steps); + + if (((v3d->flag & V3D_XR_SESSION_SURFACE) != 0) || ((v3d->flag & V3D_XR_SESSION_MIRROR) != 0)) { + /* The calculations for the grid parameters assume that the view matrix has no scale component, + * which may not be correct if the user is "shrunk" or "enlarged" by zooming in or out. + * Therefore, we need to compensate the values here. */ + float viewinvscale = len_v3( + viewinv[0]); /* Assumption is uniform scaling (all column vectors are of same length). */ + shd->grid_distance *= viewinvscale; + } } void OVERLAY_grid_cache_init(OVERLAY_Data *vedata) diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index e65fdce5f2e..1d63db80a0e 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -51,6 +51,7 @@ #include "BKE_pbvh.h" #include "BKE_pointcache.h" #include "BKE_pointcloud.h" +#include "BKE_screen.h" #include "BKE_volume.h" #include "DNA_camera_types.h" @@ -1402,6 +1403,27 @@ void DRW_draw_callbacks_post_scene(void) ED_region_draw_cb_draw(DST.draw_ctx.evil_C, DST.draw_ctx.region, REGION_DRAW_POST_VIEW); +#ifdef WITH_XR_OPENXR + /* XR callbacks (controllers, custom draw functions). */ + if ((v3d->flag & V3D_XR_SESSION_MIRROR) != 0) { + if ((v3d->flag2 & V3D_XR_SHOW_CONTROLLERS) != 0) { + ARegionType *art = WM_xr_surface_controller_region_type_get(); + if (art) { + ED_region_surface_draw_cb_draw(art, REGION_DRAW_POST_VIEW); + } + } + if ((v3d->flag2 & V3D_XR_SHOW_CUSTOM_OVERLAYS) != 0) { + SpaceType *st = BKE_spacetype_from_id(SPACE_VIEW3D); + if (st) { + ARegionType *art = BKE_regiontype_from_id(st, RGN_TYPE_XR); + if (art) { + ED_region_surface_draw_cb_draw(art, REGION_DRAW_POST_VIEW); + } + } + } + } +#endif + /* Callback can be nasty and do whatever they want with the state. * Don't trust them! */ DRW_state_reset(); @@ -1448,6 +1470,46 @@ void DRW_draw_callbacks_post_scene(void) ED_annotation_draw_view3d(DEG_get_input_scene(depsgraph), depsgraph, v3d, region, true); GPU_depth_test(GPU_DEPTH_LESS_EQUAL); } + +#ifdef WITH_XR_OPENXR + if ((v3d->flag & V3D_XR_SESSION_SURFACE) != 0) { + DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get(); + + DRW_state_reset(); + + GPU_framebuffer_bind(dfbl->overlay_fb); + + GPU_matrix_projection_set(rv3d->winmat); + GPU_matrix_set(rv3d->viewmat); + + /* Callbacks (controllers, custom draw functions). */ + if (((v3d->flag2 & V3D_XR_SHOW_CONTROLLERS) != 0) || + ((v3d->flag2 & V3D_XR_SHOW_CUSTOM_OVERLAYS) != 0)) { + GPU_depth_test(GPU_DEPTH_NONE); + GPU_apply_state(); + + if ((v3d->flag2 & V3D_XR_SHOW_CONTROLLERS) != 0) { + ARegionType *art = WM_xr_surface_controller_region_type_get(); + if (art) { + ED_region_surface_draw_cb_draw(art, REGION_DRAW_POST_VIEW); + } + } + if ((v3d->flag2 & V3D_XR_SHOW_CUSTOM_OVERLAYS) != 0) { + SpaceType *st = BKE_spacetype_from_id(SPACE_VIEW3D); + if (st) { + ARegionType *art = BKE_regiontype_from_id(st, RGN_TYPE_XR); + if (art) { + ED_region_surface_draw_cb_draw(art, REGION_DRAW_POST_VIEW); + } + } + } + + DRW_state_reset(); + } + + GPU_depth_test(GPU_DEPTH_LESS_EQUAL); + } +#endif } } diff --git a/source/blender/draw/intern/draw_manager_data.c b/source/blender/draw/intern/draw_manager_data.c index af331c86a8b..171c1e8154e 100644 --- a/source/blender/draw/intern/draw_manager_data.c +++ b/source/blender/draw/intern/draw_manager_data.c @@ -1682,10 +1682,12 @@ static void draw_frustum_bound_sphere_calc(const BoundBox *bbox, bsphere->center[0] = farcenter[0] * z / e; bsphere->center[1] = farcenter[1] * z / e; bsphere->center[2] = z; - bsphere->radius = len_v3v3(bsphere->center, farpoint); - /* Transform to world space. */ - mul_m4_v3(viewinv, bsphere->center); + /* For XR, the view matrix may contain a scale factor. Then, transforming only the center + * into world space after calculating the radius will result in incorrect behavior. */ + mul_m4_v3(viewinv, bsphere->center); /* Transform to world space. */ + mul_m4_v3(viewinv, farpoint); + bsphere->radius = len_v3v3(bsphere->center, farpoint); } } diff --git a/source/blender/editors/include/ED_space_api.h b/source/blender/editors/include/ED_space_api.h index 1a3aa7e5496..b43829c77a3 100644 --- a/source/blender/editors/include/ED_space_api.h +++ b/source/blender/editors/include/ED_space_api.h @@ -72,7 +72,8 @@ void *ED_region_draw_cb_activate(struct ARegionType *art, void (*draw)(const struct bContext *, struct ARegion *, void *), void *customdata, int type); -void ED_region_draw_cb_draw(const struct bContext *, struct ARegion *, int); +void ED_region_draw_cb_draw(const struct bContext *C, struct ARegion *region, int type); +void ED_region_surface_draw_cb_draw(struct ARegionType *art, int type); void ED_region_draw_cb_exit(struct ARegionType *, void *); void ED_region_draw_cb_remove_by_type(struct ARegionType *art, void *draw_fn, diff --git a/source/blender/editors/include/ED_transform.h b/source/blender/editors/include/ED_transform.h index bedd0e2fa35..c29d477f71e 100644 --- a/source/blender/editors/include/ED_transform.h +++ b/source/blender/editors/include/ED_transform.h @@ -31,8 +31,10 @@ extern "C" { struct Object; struct bContext; +struct ViewLayer; struct wmKeyConfig; struct wmOperatorType; +struct wmTimer; void ED_keymap_transform(struct wmKeyConfig *keyconf); void transform_operatortypes(void); @@ -193,6 +195,19 @@ int ED_transform_calc_gizmo_stats(const struct bContext *C, const struct TransformCalcParams *params, struct TransformBounds *tbounds); +/* transform_convert.c */ +void ED_transform_animrecord_check_state(struct Scene *scene, + struct wmTimer *animtimer, + struct Object *ob); + +/* transform_convert_object.c */ +void ED_transform_autokeyframe_object(struct bContext *C, + struct Scene *scene, + struct ViewLayer *view_layer, + struct Object *ob, + int tmode); +bool ED_transform_motionpath_need_update_object(struct Scene *scene, struct Object *ob); + #ifdef __cplusplus } #endif diff --git a/source/blender/editors/include/ED_transform_snap_object_context.h b/source/blender/editors/include/ED_transform_snap_object_context.h index 42e73bbf744..8938e1114d9 100644 --- a/source/blender/editors/include/ED_transform_snap_object_context.h +++ b/source/blender/editors/include/ED_transform_snap_object_context.h @@ -44,6 +44,8 @@ typedef enum { SNAP_NOT_SELECTED = 1, SNAP_NOT_ACTIVE = 2, SNAP_ONLY_ACTIVE = 3, + SNAP_SELECTED = 4, + SNAP_SELECTABLE = 5, } eSnapSelect; typedef enum { diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h index cf8dcbd7995..9076781bce5 100644 --- a/source/blender/editors/include/ED_view3d.h +++ b/source/blender/editors/include/ED_view3d.h @@ -595,6 +595,7 @@ void ED_view3d_viewcontext_init(struct bContext *C, void ED_view3d_viewcontext_init_object(struct ViewContext *vc, struct Object *obact); void view3d_operator_needs_opengl(const struct bContext *C); void view3d_region_operator_needs_opengl(struct wmWindow *win, struct ARegion *region); +bool object_deselect_all_except(struct ViewLayer *view_layer, struct Base *b); /* XXX should move to BLI_math */ bool edge_inside_circle(const float cent[2], @@ -776,12 +777,27 @@ void ED_view3d_buttons_region_layout_ex(const struct bContext *C, bool ED_view3d_local_collections_set(struct Main *bmain, struct View3D *v3d); void ED_view3d_local_collections_reset(struct bContext *C, const bool reset_all); +void ED_view3d_view_params_get(const struct View3D *v3d, + const struct RegionView3D *rv3d, + float *r_lens, + float *r_clip_start, + float *r_clip_end, + float r_viewmat[4][4]); +void ED_view3d_view_params_set(struct Depsgraph *depsgraph, + struct Scene *scene, + struct View3D *v3d, + struct ARegion *region, + const float lens, + const float clip_start, + const float clip_end, + const float viewmat[4][4]); + #ifdef WITH_XR_OPENXR void ED_view3d_xr_mirror_update(const struct ScrArea *area, const struct View3D *v3d, const 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, diff --git a/source/blender/editors/include/ED_view3d_offscreen.h b/source/blender/editors/include/ED_view3d_offscreen.h index c490e96031f..8b695e61a35 100644 --- a/source/blender/editors/include/ED_view3d_offscreen.h +++ b/source/blender/editors/include/ED_view3d_offscreen.h @@ -60,7 +60,7 @@ void ED_view3d_draw_offscreen(struct Depsgraph *depsgraph, void ED_view3d_draw_offscreen_simple(struct Depsgraph *depsgraph, struct Scene *scene, struct View3DShading *shading_override, - int drawtype, + eDrawType drawtype, int winx, int winy, unsigned int draw_flags, @@ -68,6 +68,7 @@ void ED_view3d_draw_offscreen_simple(struct Depsgraph *depsgraph, const float winmat[4][4], float clip_start, float clip_end, + bool is_xr_surface, bool is_image_render, bool draw_background, const char *viewname, diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index f642895f64e..59d688ad2d0 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -2192,6 +2192,7 @@ void uiTemplateEditModeSelection(uiLayout *layout, struct bContext *C); void uiTemplateReportsBanner(uiLayout *layout, struct bContext *C); void uiTemplateInputStatus(uiLayout *layout, struct bContext *C); void uiTemplateKeymapItemProperties(uiLayout *layout, struct PointerRNA *ptr); +void uiTemplateXrActionmapItemProperties(uiLayout *layout, struct PointerRNA *ptr); bool uiTemplateEventFromKeymapItem(struct uiLayout *layout, const char *text, diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 8fcc704a301..f27ff9694c2 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -113,5 +113,8 @@ if(WIN32 OR APPLE) endif() endif() +if(WITH_XR_OPENXR) + add_definitions(-DWITH_XR_OPENXR) +endif() blender_add_lib(bf_editor_interface "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 320371ad9ea..acfac544ed3 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -6236,6 +6236,44 @@ void uiTemplateKeymapItemProperties(uiLayout *layout, PointerRNA *ptr) /** \} */ /* -------------------------------------------------------------------- */ +/** \name XR Actionmap Template + * \{ */ + +#ifdef WITH_XR_OPENXR +static void xr_actionmap_item_modified(bContext *UNUSED(C), void *ami_p, void *UNUSED(unused)) +{ + XrActionMapItem *ami = (XrActionMapItem *)ami_p; + WM_xr_actionconfig_update_tag(NULL, ami); +} +#endif + +void uiTemplateXrActionmapItemProperties(uiLayout *layout, PointerRNA *ptr) +{ +#ifdef WITH_XR_OPENXR + PointerRNA propptr = RNA_pointer_get(ptr, "op_properties"); + + if (propptr.data) { + uiBut *but = uiLayoutGetBlock(layout)->buttons.last; + + WM_operator_properties_sanitize(&propptr, false); + /* Use same template as keymap item properties. */ + template_keymap_item_properties(layout, NULL, &propptr); + + for (; but; but = but->next) { + if (but->rnaprop) { + UI_but_func_set(but, xr_actionmap_item_modified, ptr->data, NULL); + UI_but_flag_enable(but, UI_BUT_UPDATE_DELAY); + } + } + } +#else + UNUSED_VARS(layout, ptr); +#endif +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Event Icon Template * \{ */ diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c index 3da283089c5..149067a94fe 100644 --- a/source/blender/editors/space_api/spacetypes.c +++ b/source/blender/editors/space_api/spacetypes.c @@ -264,9 +264,9 @@ void ED_region_draw_cb_exit(ARegionType *art, void *handle) } } -void ED_region_draw_cb_draw(const bContext *C, ARegion *region, int type) +static void ed_region_draw_cb_draw(const bContext *C, ARegion *region, ARegionType *art, int type) { - LISTBASE_FOREACH_MUTABLE (RegionDrawCB *, rdc, ®ion->type->drawcalls) { + LISTBASE_FOREACH_MUTABLE (RegionDrawCB *, rdc, &art->drawcalls) { if (rdc->type == type) { rdc->draw(C, region, rdc->customdata); @@ -276,6 +276,16 @@ void ED_region_draw_cb_draw(const bContext *C, ARegion *region, int type) } } +void ED_region_draw_cb_draw(const bContext *C, ARegion *region, int type) +{ + ed_region_draw_cb_draw(C, region, region->type, type); +} + +void ED_region_surface_draw_cb_draw(ARegionType *art, int type) +{ + ed_region_draw_cb_draw(NULL, NULL, art, type); +} + void ED_region_draw_cb_remove_by_type(ARegionType *art, void *draw_fn, void (*free)(void *)) { LISTBASE_FOREACH_MUTABLE (RegionDrawCB *, rdc, &art->drawcalls) { diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index f68a4d78a00..bedc24a6287 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -1798,5 +1798,10 @@ void ED_spacetype_view3d(void) art = ED_area_type_hud(st->spaceid); BLI_addhead(&st->regiontypes, art); + /* regions: xr */ + art = MEM_callocN(sizeof(ARegionType), "spacetype view3d xr region"); + art->regionid = RGN_TYPE_XR; + BLI_addhead(&st->regiontypes, art); + BKE_spacetype_register(st); } diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c index 86f79718a68..e7be9903b89 100644 --- a/source/blender/editors/space_view3d/view3d_draw.c +++ b/source/blender/editors/space_view3d/view3d_draw.c @@ -328,19 +328,44 @@ static void view3d_xr_mirror_setup(const wmWindowManager *wm, ARegion *region, const rcti *rect) { - RegionView3D *rv3d = region->regiondata; float viewmat[4][4]; const float lens_old = v3d->lens; - - if (!WM_xr_session_state_viewer_pose_matrix_info_get(&wm->xr, viewmat, &v3d->lens)) { + const float clip_start_old = v3d->clip_start; + const float clip_end_old = v3d->clip_end; + /* Here we need to use the viewmat from the selection eye instead of the eye centroid because + * this function may be called from a GPU select operation. In that case we need to match the + * selection eye's view, which was used to project 3D to 2D, for a correct result. */ + const bool from_selection_eye = true; + + if (!WM_xr_session_state_viewer_pose_matrix_info_get( + &wm->xr, from_selection_eye, viewmat, &v3d->lens, &v3d->clip_start, &v3d->clip_end)) { /* Can't get info from XR session, use fallback values. */ - copy_m4_m4(viewmat, rv3d->viewmat); + copy_m4_m4(viewmat, ((RegionView3D *)region->regiondata)->viewmat); v3d->lens = lens_old; + v3d->clip_start = clip_start_old; + v3d->clip_end = clip_end_old; } view3d_main_region_setup_view(depsgraph, scene, v3d, region, viewmat, NULL, rect); - /* Reset overridden View3D data */ + if ((wm->xr.session_settings.draw_flags & V3D_OFSDRAW_XR_SHOW_CONTROLLERS) != 0) { + v3d->flag2 |= V3D_XR_SHOW_CONTROLLERS; + } + else { + v3d->flag2 &= ~V3D_XR_SHOW_CONTROLLERS; + } + if ((wm->xr.session_settings.draw_flags & V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS) != 0) { + v3d->flag2 |= V3D_XR_SHOW_CUSTOM_OVERLAYS; + } + else { + v3d->flag2 &= ~V3D_XR_SHOW_CUSTOM_OVERLAYS; + } + /* Hide navigation gizmo. */ + v3d->gizmo_flag |= V3D_GIZMO_HIDE_NAVIGATE; + + /* Reset overridden View3D data. */ v3d->lens = lens_old; + v3d->clip_start = clip_start_old; + v3d->clip_end = clip_end_old; } #endif /* WITH_XR_OPENXR */ @@ -1750,7 +1775,7 @@ void ED_view3d_draw_offscreen(Depsgraph *depsgraph, void ED_view3d_draw_offscreen_simple(Depsgraph *depsgraph, Scene *scene, View3DShading *shading_override, - int drawtype, + eDrawType drawtype, int winx, int winy, uint draw_flags, @@ -1758,6 +1783,7 @@ void ED_view3d_draw_offscreen_simple(Depsgraph *depsgraph, const float winmat[4][4], float clip_start, float clip_end, + bool is_xr_surface, bool is_image_render, bool draw_background, const char *viewname, @@ -1787,23 +1813,37 @@ void ED_view3d_draw_offscreen_simple(Depsgraph *depsgraph, v3d.shading.flag = V3D_SHADING_SCENE_WORLD | V3D_SHADING_SCENE_LIGHTS; } - if (draw_flags & V3D_OFSDRAW_SHOW_ANNOTATION) { - v3d.flag2 |= V3D_SHOW_ANNOTATION; + if ((draw_flags & ~V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS) == V3D_OFSDRAW_NONE) { + v3d.flag2 = V3D_HIDE_OVERLAYS; } - if (draw_flags & V3D_OFSDRAW_SHOW_GRIDFLOOR) { - v3d.gridflag |= V3D_SHOW_FLOOR | V3D_SHOW_X | V3D_SHOW_Y; - v3d.grid = 1.0f; - v3d.gridlines = 16; - v3d.gridsubdiv = 10; - - /* Show grid, disable other overlays (set all available _HIDE_ flags). */ + else { + if (draw_flags & V3D_OFSDRAW_SHOW_ANNOTATION) { + v3d.flag2 |= V3D_SHOW_ANNOTATION; + } + if (draw_flags & V3D_OFSDRAW_SHOW_GRIDFLOOR) { + v3d.gridflag |= V3D_SHOW_FLOOR | V3D_SHOW_X | V3D_SHOW_Y; + v3d.grid = 1.0f; + v3d.gridlines = 16; + v3d.gridsubdiv = 10; + } + if (draw_flags & V3D_OFSDRAW_SHOW_SELECTION) { + v3d.flag |= V3D_SELECT_OUTLINE; + } + if (draw_flags & V3D_OFSDRAW_XR_SHOW_CONTROLLERS) { + v3d.flag2 |= V3D_XR_SHOW_CONTROLLERS; + } + if (draw_flags & V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS) { + v3d.flag2 |= V3D_XR_SHOW_CUSTOM_OVERLAYS; + } + /* Disable other overlays (set all available _HIDE_ flags). */ v3d.overlay.flag |= V3D_OVERLAY_HIDE_CURSOR | V3D_OVERLAY_HIDE_TEXT | V3D_OVERLAY_HIDE_MOTION_PATHS | V3D_OVERLAY_HIDE_BONES | V3D_OVERLAY_HIDE_OBJECT_XTRAS | V3D_OVERLAY_HIDE_OBJECT_ORIGINS; v3d.flag |= V3D_HIDE_HELPLINES; } - else { - v3d.flag2 = V3D_HIDE_OVERLAYS; + + if (is_xr_surface) { + v3d.flag |= V3D_XR_SESSION_SURFACE; } rv3d.persp = RV3D_PERSP; diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c index 07f1f8a753c..ac5a06d22bb 100644 --- a/source/blender/editors/space_view3d/view3d_select.c +++ b/source/blender/editors/space_view3d/view3d_select.c @@ -169,7 +169,7 @@ static bool object_deselect_all_visible(ViewLayer *view_layer, View3D *v3d) } /* deselect all except b */ -static bool object_deselect_all_except(ViewLayer *view_layer, Base *b) +bool object_deselect_all_except(ViewLayer *view_layer, Base *b) { bool changed = false; LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { @@ -2818,6 +2818,57 @@ static int view3d_select_invoke(bContext *C, wmOperator *op, const wmEvent *even return WM_operator_flag_only_pass_through_on_press(retval, event); } +static int view3d_select_invoke_3d(bContext *C, wmOperator *op, const wmEvent *event) +{ + BLI_assert(event->type == EVT_XR_ACTION); + BLI_assert(event->custom == EVT_DATA_XR); + BLI_assert(event->customdata); + + const wmXrActionData *actiondata = event->customdata; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + View3D *v3d = CTX_wm_view3d(C); + ARegion *region = CTX_wm_region(C); + RegionView3D *rv3d = region->regiondata; + wmWindowManager *wm = CTX_wm_manager(C); + wmXrData *xr = &wm->xr; + float lens_prev; + float clip_start_prev, clip_end_prev; + float viewmat_prev[4][4]; + int mval[2]; + int retval; + + /* Since this function is called in a window context, we need to replace the + * window view parameters with the XR surface counterparts to get a correct + * result for GPU select. */ + ED_view3d_view_params_get(v3d, rv3d, &lens_prev, &clip_start_prev, &clip_end_prev, viewmat_prev); + ED_view3d_view_params_set(depsgraph, + scene, + v3d, + region, + actiondata->eye_lens, + xr->session_settings.clip_start, + xr->session_settings.clip_end, + actiondata->eye_viewmat); + + map_to_pixel(mval, + actiondata->controller_loc, + actiondata->eye_viewmat, + rv3d->winmat, + region->winx, + region->winy); + + RNA_int_set_array(op->ptr, "location", mval); + + retval = view3d_select_exec(C, op); + + /* Restore window view. */ + ED_view3d_view_params_set( + depsgraph, scene, v3d, region, lens_prev, clip_start_prev, clip_end_prev, viewmat_prev); + + return retval; +} + void VIEW3D_OT_select(wmOperatorType *ot) { PropertyRNA *prop; @@ -2829,6 +2880,7 @@ void VIEW3D_OT_select(wmOperatorType *ot) /* api callbacks */ ot->invoke = view3d_select_invoke; + ot->invoke_3d = view3d_select_invoke_3d; ot->exec = view3d_select_exec; ot->poll = ED_operator_view3d_active; @@ -3698,8 +3750,10 @@ void VIEW3D_OT_select_box(wmOperatorType *ot) /* api callbacks */ ot->invoke = WM_gesture_box_invoke; + ot->invoke_3d = WM_gesture_box_invoke_3d; ot->exec = view3d_box_select_exec; ot->modal = WM_gesture_box_modal; + ot->modal_3d = WM_gesture_box_modal_3d; ot->poll = view3d_selectable_data; ot->cancel = WM_gesture_box_cancel; diff --git a/source/blender/editors/space_view3d/view3d_view.c b/source/blender/editors/space_view3d/view3d_view.c index f5da7c14a88..df085faab7d 100644 --- a/source/blender/editors/space_view3d/view3d_view.c +++ b/source/blender/editors/space_view3d/view3d_view.c @@ -1692,6 +1692,44 @@ void ED_view3d_local_collections_reset(struct bContext *C, const bool reset_all) /** \name XR Functionality * \{ */ +/* Used for invoke_3d/modal_3d (XR) operators. */ +void ED_view3d_view_params_get(const struct View3D *v3d, + const struct RegionView3D *rv3d, + float *r_lens, + float *r_clip_start, + float *r_clip_end, + float r_viewmat[4][4]) +{ + *r_lens = v3d->lens; + *r_clip_start = v3d->clip_start; + *r_clip_end = v3d->clip_end; + + if (r_viewmat) { + copy_m4_m4(r_viewmat, rv3d->viewmat); + } +} + +void ED_view3d_view_params_set(struct Depsgraph *depsgraph, + struct Scene *scene, + struct View3D *v3d, + struct ARegion *region, + const float lens, + const float clip_start, + const float clip_end, + const float viewmat[4][4]) +{ + v3d->lens = lens; + v3d->clip_start = clip_start; + v3d->clip_end = clip_end; + + if (viewmat) { + ED_view3d_update_viewmat(depsgraph, scene, v3d, region, viewmat, NULL, NULL, false); + } + else { + view3d_winmatrix_set(depsgraph, region, v3d, NULL); + } +} + #ifdef WITH_XR_OPENXR static void view3d_xr_mirror_begin(RegionView3D *rv3d) @@ -1729,8 +1767,6 @@ void ED_view3d_xr_shading_update(wmWindowManager *wm, const View3D *v3d, const S { if (v3d->runtime.flag & V3D_RUNTIME_XR_SESSION_ROOT) { View3DShading *xr_shading = &wm->xr.session_settings.shading; - /* Flags that shouldn't be overridden by the 3D View shading. */ - const int flag_copy = V3D_SHADING_WORLD_ORIENTATION; BLI_assert(WM_xr_session_exists(&wm->xr)); @@ -1748,9 +1784,7 @@ void ED_view3d_xr_shading_update(wmWindowManager *wm, const View3D *v3d, const S } /* Copy shading from View3D to VR view. */ - const int old_xr_shading_flag = xr_shading->flag; *xr_shading = v3d->shading; - xr_shading->flag = (xr_shading->flag & ~flag_copy) | (old_xr_shading_flag & flag_copy); if (v3d->shading.prop) { xr_shading->prop = IDP_CopyProperty(xr_shading->prop); } diff --git a/source/blender/editors/transform/CMakeLists.txt b/source/blender/editors/transform/CMakeLists.txt index 64a720322c1..ccf9f9adf55 100644 --- a/source/blender/editors/transform/CMakeLists.txt +++ b/source/blender/editors/transform/CMakeLists.txt @@ -129,5 +129,8 @@ if(WITH_INTERNATIONAL) add_definitions(-DWITH_INTERNATIONAL) endif() +if(WITH_XR_OPENXR) + add_definitions(-DWITH_XR_OPENXR) +endif() blender_add_lib(bf_editor_transform "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index 557fa79e7ac..e11e36cc712 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -1597,11 +1597,9 @@ void transform_convert_clip_mirror_modifier_apply(TransDataContainer *tc) } /* for the realtime animation recording feature, handle overlapping data */ -void animrecord_check_state(TransInfo *t, struct Object *ob) +void ED_transform_animrecord_check_state(Scene *scene, wmTimer *animtimer, struct Object *ob) { - Scene *scene = t->scene; ID *id = &ob->id; - wmTimer *animtimer = t->animtimer; ScreenAnimData *sad = (animtimer) ? animtimer->customdata : NULL; /* sanity checks */ diff --git a/source/blender/editors/transform/transform_convert.h b/source/blender/editors/transform/transform_convert.h index 66d84bca2d2..308356ceed9 100644 --- a/source/blender/editors/transform/transform_convert.h +++ b/source/blender/editors/transform/transform_convert.h @@ -65,7 +65,6 @@ struct TransDataCurveHandleFlags *initTransDataCurveHandles(TransData *td, struc char transform_convert_frame_side_dir_get(TransInfo *t, float cframe); bool FrameOnMouseSide(char side, float frame, float cframe); void transform_convert_clip_mirror_modifier_apply(TransDataContainer *tc); -void animrecord_check_state(TransInfo *t, struct Object *ob); /* transform_convert_action.c */ void createTransActionData(bContext *C, TransInfo *t); diff --git a/source/blender/editors/transform/transform_convert_armature.c b/source/blender/editors/transform/transform_convert_armature.c index 1bbbbc6294e..d19ff123037 100644 --- a/source/blender/editors/transform/transform_convert_armature.c +++ b/source/blender/editors/transform/transform_convert_armature.c @@ -1449,7 +1449,7 @@ void recalcData_pose(TransInfo *t) /* XXX: this currently doesn't work, since flags aren't set yet! */ int targetless_ik = (t->flag & T_AUTOIK); - animrecord_check_state(t, ob); + ED_transform_animrecord_check_state(t->scene, t->animtimer, ob); autokeyframe_pose(t->context, t->scene, ob, t->mode, targetless_ik); } diff --git a/source/blender/editors/transform/transform_convert_object.c b/source/blender/editors/transform/transform_convert_object.c index 1acd8787f51..09aa4314b32 100644 --- a/source/blender/editors/transform/transform_convert_object.c +++ b/source/blender/editors/transform/transform_convert_object.c @@ -728,7 +728,7 @@ void createTransObject(bContext *C, TransInfo *t) * \note Context may not always be available, * so must check before using it as it's a luxury for a few cases. */ -static void autokeyframe_object( +void ED_transform_autokeyframe_object( bContext *C, Scene *scene, ViewLayer *view_layer, Object *ob, int tmode) { Main *bmain = CTX_data_main(C); @@ -850,7 +850,7 @@ static void autokeyframe_object( /* Return if we need to update motion paths, only if they already exist, * and we will insert a keyframe at the end of transform. */ -static bool motionpath_need_update_object(Scene *scene, Object *ob) +bool ED_transform_motionpath_need_update_object(Scene *scene, Object *ob) { /* XXX: there's potential here for problems with unkeyed rotations/scale, * but for now (until proper data-locality for baking operations), @@ -895,11 +895,11 @@ void recalcData_objects(TransInfo *t) /* TODO: autokeyframe calls need some setting to specify to add samples * (FPoints) instead of keyframes? */ if ((t->animtimer) && IS_AUTOKEY_ON(t->scene)) { - animrecord_check_state(t, ob); - autokeyframe_object(t->context, t->scene, t->view_layer, ob, t->mode); + ED_transform_animrecord_check_state(t->scene, t->animtimer, ob); + ED_transform_autokeyframe_object(t->context, t->scene, t->view_layer, ob, t->mode); } - motionpath_update |= motionpath_need_update_object(t->scene, ob); + motionpath_update |= ED_transform_motionpath_need_update_object(t->scene, ob); /* sets recalc flags fully, instead of flushing existing ones * otherwise proxies don't function correctly @@ -971,10 +971,10 @@ void special_aftertrans_update__object(bContext *C, TransInfo *t) /* Set auto-key if necessary. */ if (!canceled) { - autokeyframe_object(C, t->scene, t->view_layer, ob, t->mode); + ED_transform_autokeyframe_object(C, t->scene, t->view_layer, ob, t->mode); } - motionpath_update |= motionpath_need_update_object(t->scene, ob); + motionpath_update |= ED_transform_motionpath_need_update_object(t->scene, ob); /* Restore rigid body transform. */ if (ob->rigidbody_object && canceled) { diff --git a/source/blender/editors/transform/transform_ops.c b/source/blender/editors/transform/transform_ops.c index 3a4a9342e18..7989ea3f8ab 100644 --- a/source/blender/editors/transform/transform_ops.c +++ b/source/blender/editors/transform/transform_ops.c @@ -470,6 +470,68 @@ static int transform_modal(bContext *C, wmOperator *op, const wmEvent *event) return exit_code; } +static int transform_modal_3d(bContext *C, wmOperator *op, const wmEvent *event) +{ + BLI_assert(event->type == EVT_XR_ACTION); + BLI_assert(event->custom == EVT_DATA_XR); + BLI_assert(event->customdata); + + const wmXrActionData *actiondata = event->customdata; + TransInfo *t = op->customdata; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + View3D *v3d = CTX_wm_view3d(C); + ARegion *region = CTX_wm_region(C); + RegionView3D *rv3d = region->regiondata; + wmWindowManager *wm = CTX_wm_manager(C); + wmXrData *xr = &wm->xr; + float lens_prev; + float clip_start_prev, clip_end_prev; + float viewmat_prev[4][4]; + int retval; + + wmEvent event_mut; + memcpy(&event_mut, event, sizeof(wmEvent)); + + /* Replace window view parameters with XR surface counterparts. */ + ED_view3d_view_params_get(v3d, rv3d, &lens_prev, &clip_start_prev, &clip_end_prev, viewmat_prev); + ED_view3d_view_params_set( + depsgraph, + scene, + v3d, + region, + actiondata->eye_lens, + xr->session_settings.clip_start, + xr->session_settings.clip_end, + t->viewmat); /* Use viewmat from when transform was invoked instead of latest XR viewmat. */ + + map_to_pixel(event_mut.mval, + actiondata->controller_loc, + t->viewmat, + rv3d->winmat, + region->winx, + region->winy); + + if (event->val == KM_PRESS) { + event_mut.type = MOUSEMOVE; + } + else if (event->val == KM_RELEASE) { + event_mut.type = LEFTMOUSE; + } + else { + /* XR events currently only support press and release. */ + BLI_assert(false); + } + + retval = transform_modal(C, op, &event_mut); + + /* Restore window view. */ + ED_view3d_view_params_set( + depsgraph, scene, v3d, region, lens_prev, clip_start_prev, clip_end_prev, viewmat_prev); + + return retval; +} + static void transform_cancel(bContext *C, wmOperator *op) { TransInfo *t = op->customdata; @@ -530,6 +592,56 @@ static int transform_invoke(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_RUNNING_MODAL; } +static int transform_invoke_3d(bContext *C, wmOperator *op, const wmEvent *event) +{ + BLI_assert(event->type == EVT_XR_ACTION); + BLI_assert(event->custom == EVT_DATA_XR); + BLI_assert(event->customdata); + + const wmXrActionData *actiondata = event->customdata; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + View3D *v3d = CTX_wm_view3d(C); + ARegion *region = CTX_wm_region(C); + RegionView3D *rv3d = region->regiondata; + wmWindowManager *wm = CTX_wm_manager(C); + wmXrData *xr = &wm->xr; + float lens_prev; + float clip_start_prev, clip_end_prev; + float viewmat_prev[4][4]; + int retval; + + wmEvent event_mut; + memcpy(&event_mut, event, sizeof(wmEvent)); + event_mut.type = LEFTMOUSE; + + /* Replace window view parameters with XR surface counterparts. */ + ED_view3d_view_params_get(v3d, rv3d, &lens_prev, &clip_start_prev, &clip_end_prev, viewmat_prev); + ED_view3d_view_params_set(depsgraph, + scene, + v3d, + region, + actiondata->eye_lens, + xr->session_settings.clip_start, + xr->session_settings.clip_end, + actiondata->eye_viewmat); + + map_to_pixel(event_mut.mval, + actiondata->controller_loc, + actiondata->eye_viewmat, + rv3d->winmat, + region->winx, + region->winy); + + retval = transform_invoke(C, op, &event_mut); + + /* Restore window view. */ + ED_view3d_view_params_set( + depsgraph, scene, v3d, region, lens_prev, clip_start_prev, clip_end_prev, viewmat_prev); + + return retval; +} + static bool transform_poll_property(const bContext *UNUSED(C), wmOperator *op, const PropertyRNA *prop) @@ -747,8 +859,10 @@ static void TRANSFORM_OT_translate(struct wmOperatorType *ot) /* api callbacks */ ot->invoke = transform_invoke; + ot->invoke_3d = transform_invoke_3d; ot->exec = transform_exec; ot->modal = transform_modal; + ot->modal_3d = transform_modal_3d; ot->cancel = transform_cancel; ot->poll = ED_operator_screenactive; ot->poll_property = transform_poll_property; @@ -774,8 +888,10 @@ static void TRANSFORM_OT_resize(struct wmOperatorType *ot) /* api callbacks */ ot->invoke = transform_invoke; + ot->invoke_3d = transform_invoke_3d; ot->exec = transform_exec; ot->modal = transform_modal; + ot->modal_3d = transform_modal_3d; ot->cancel = transform_cancel; ot->poll = ED_operator_screenactive; ot->poll_property = transform_poll_property; @@ -862,8 +978,10 @@ static void TRANSFORM_OT_rotate(struct wmOperatorType *ot) /* api callbacks */ ot->invoke = transform_invoke; + ot->invoke_3d = transform_invoke_3d; ot->exec = transform_exec; ot->modal = transform_modal; + ot->modal_3d = transform_modal_3d; ot->cancel = transform_cancel; ot->poll = transform_rotate_poll; ot->poll_property = transform_poll_property; @@ -1264,8 +1382,10 @@ static void TRANSFORM_OT_transform(struct wmOperatorType *ot) /* api callbacks */ ot->invoke = transform_invoke; + ot->invoke_3d = transform_invoke_3d; ot->exec = transform_exec; ot->modal = transform_modal; + ot->modal_3d = transform_modal_3d; ot->cancel = transform_cancel; ot->poll = ED_operator_screenactive; ot->poll_property = transform_poll_property; diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c index 883a89b8467..70297fad4ff 100644 --- a/source/blender/editors/transform/transform_snap_object.c +++ b/source/blender/editors/transform/transform_snap_object.c @@ -499,6 +499,16 @@ static void iter_snap_objects(SnapObjectContext *sctx, continue; } } + else if (snap_select == SNAP_SELECTED) { + if (!(base->flag & BASE_SELECTED) && !(base->flag_legacy & BA_WAS_SEL)) { + continue; + } + } + else if (snap_select == SNAP_SELECTABLE) { + if (!(base->flag & BASE_SELECTABLE)) { + continue; + } + } Object *obj_eval = DEG_get_evaluated_object(depsgraph, base->object); if (obj_eval->transflag & OB_DUPLI || BKE_object_has_geometry_set_instances(obj_eval)) { diff --git a/source/blender/makesdna/DNA_screen_types.h b/source/blender/makesdna/DNA_screen_types.h index d5b7458ae7b..42dd6d2181b 100644 --- a/source/blender/makesdna/DNA_screen_types.h +++ b/source/blender/makesdna/DNA_screen_types.h @@ -666,8 +666,11 @@ typedef enum eRegionType { RGN_TYPE_EXECUTE = 10, RGN_TYPE_FOOTER = 11, RGN_TYPE_TOOL_HEADER = 12, + /* Region type used exclusively by internal code and add-ons to register draw callbacks to the XR + context (surface, mirror view). Does not represent any real region. */ + RGN_TYPE_XR = 13, -#define RGN_TYPE_LEN (RGN_TYPE_TOOL_HEADER + 1) +#define RGN_TYPE_LEN (RGN_TYPE_XR + 1) } eRegionType; /* use for function args */ diff --git a/source/blender/makesdna/DNA_view3d_enums.h b/source/blender/makesdna/DNA_view3d_enums.h index aec52da1bf9..59012a0cd8d 100644 --- a/source/blender/makesdna/DNA_view3d_enums.h +++ b/source/blender/makesdna/DNA_view3d_enums.h @@ -30,6 +30,9 @@ typedef enum eV3DOffscreenDrawFlag { V3D_OFSDRAW_SHOW_ANNOTATION = (1 << 0), V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS = (1 << 1), V3D_OFSDRAW_SHOW_GRIDFLOOR = (1 << 2), + V3D_OFSDRAW_SHOW_SELECTION = (1 << 3), + V3D_OFSDRAW_XR_SHOW_CONTROLLERS = (1 << 4), + V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS = (1 << 5), } eV3DOffscreenDrawFlag; /** #View3DShading.light */ diff --git a/source/blender/makesdna/DNA_view3d_types.h b/source/blender/makesdna/DNA_view3d_types.h index 4d88f6f0c15..fafc470b95a 100644 --- a/source/blender/makesdna/DNA_view3d_types.h +++ b/source/blender/makesdna/DNA_view3d_types.h @@ -373,6 +373,7 @@ typedef struct View3D { #define V3D_HIDE_HELPLINES (1 << 2) #define V3D_FLAG_UNUSED_2 (1 << 3) /* cleared */ #define V3D_XR_SESSION_MIRROR (1 << 4) +#define V3D_XR_SESSION_SURFACE (1 << 5) #define V3D_FLAG_UNUSED_10 (1 << 10) /* cleared */ #define V3D_SELECT_OUTLINE (1 << 11) @@ -465,6 +466,8 @@ enum { #define V3D_FLAG2_UNUSED_13 (1 << 13) /* cleared */ #define V3D_FLAG2_UNUSED_14 (1 << 14) /* cleared */ #define V3D_FLAG2_UNUSED_15 (1 << 15) /* cleared */ +#define V3D_XR_SHOW_CONTROLLERS (1 << 16) +#define V3D_XR_SHOW_CUSTOM_OVERLAYS (1 << 17) /** #View3D.gp_flag (short) */ #define V3D_GP_FADE_OBJECTS (1 << 0) /* Fade all non GP objects */ diff --git a/source/blender/makesdna/DNA_xr_types.h b/source/blender/makesdna/DNA_xr_types.h index a9d427777f7..57347c3d710 100644 --- a/source/blender/makesdna/DNA_xr_types.h +++ b/source/blender/makesdna/DNA_xr_types.h @@ -26,15 +26,17 @@ extern "C" { #endif +struct XrActionConfig; + /* -------------------------------------------------------------------- */ typedef struct XrSessionSettings { /** Shading settings, struct shared with 3D-View so settings are the same. */ struct View3DShading shading; - char _pad[7]; - - char base_pose_type; /* #eXRSessionBasePoseType */ + float base_scale; + char _pad[3]; + char base_pose_type; /* eXrSessionBasePoseType */ /** Object to take the location and rotation as base position from. */ Object *base_pose_object; float base_pose_location[3]; @@ -42,12 +44,30 @@ typedef struct XrSessionSettings { /** View3D draw flags (V3D_OFSDRAW_NONE, V3D_OFSDRAW_SHOW_ANNOTATION, ...). */ char draw_flags; - char _pad2[3]; + /** Draw style for controller visualization. */ + char controller_draw_style; + /** The eye (view) used when projecting 3D to 2D (e.g. when performing GPU select). */ + char selection_eye; + char _pad2; /** Clipping distance. */ float clip_start, clip_end; - int flag; + int flag; /* eXrSessionFlag */ + + /** Known action configurations. */ + ListBase actionconfigs; /* XrActionConfig */ + /** Default configuration. */ + struct XrActionConfig *defaultconf; + /** Addon configuration. */ + struct XrActionConfig *addonconf; + /** User configuration. */ + struct XrActionConfig *userconf; + + /** Objects to bind to headset/controller poses. */ + ListBase mocap_objects; /* XrMotionCaptureObject */ + short sel_mocap_object; + char _pad3[6]; } XrSessionSettings; typedef enum eXrSessionFlag { @@ -55,11 +75,23 @@ typedef enum eXrSessionFlag { XR_SESSION_USE_ABSOLUTE_TRACKING = (1 << 1), } eXrSessionFlag; -typedef enum eXRSessionBasePoseType { +typedef enum eXrSessionBasePoseType { XR_BASE_POSE_SCENE_CAMERA = 0, XR_BASE_POSE_OBJECT = 1, XR_BASE_POSE_CUSTOM = 2, -} eXRSessionBasePoseType; +} eXrSessionBasePoseType; + +typedef enum eXrSessionControllerDrawStyle { + XR_CONTROLLER_DRAW_DARK = 0, + XR_CONTROLLER_DRAW_LIGHT = 1, + XR_CONTROLLER_DRAW_DARK_RAY = 2, + 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 { @@ -94,11 +126,11 @@ typedef enum eXrHapticFlag { XR_HAPTIC_REPEAT = (1 << 3), } eXrHapticFlag; -/** - * For axis-based inputs (thumb-stick/track-pad/etc). - * Determines the region for action execution (mutually exclusive per axis). - */ typedef enum eXrAxisFlag { + /** + * For axis-based inputs (thumb-stick/track-pad/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), @@ -111,6 +143,11 @@ typedef enum eXrPoseFlag { XR_POSE_AIM = (1 << 1), } eXrPoseFlag; +typedef enum eXrMotionCaptureFlag { + XR_MOCAP_OBJECT_ENABLE = (1 << 0), + XR_MOCAP_OBJECT_AUTOKEY = (1 << 1), +} eXrMotionCaptureFlag; + /* -------------------------------------------------------------------- */ typedef struct XrActionMapBinding { @@ -151,7 +188,7 @@ typedef struct XrActionMapItem { char user_path1[64]; /** Operator to be called on XR events. */ - char op[64]; /* OP_MAX_TYPENAME */ + char op[64]; /** Operator properties, assigned to ptr->data and can be written to a file. */ IDProperty *op_properties; /** RNA pointer to access properties. */ @@ -171,10 +208,15 @@ typedef struct XrActionMapItem { float haptic_amplitude; short selbinding; - char _pad3[2]; + short flag; ListBase bindings; /* XrActionMapBinding */ } XrActionMapItem; +/** #XrActionMapItem.flag */ +enum { + XR_ACTIONMAP_ITEM_UPDATE = (1 << 0), +}; + /* -------------------------------------------------------------------- */ typedef struct XrActionMap { @@ -185,9 +227,53 @@ typedef struct XrActionMap { ListBase items; /* XrActionMapItem */ short selitem; - char _pad[6]; + short flag; + char _pad[4]; } XrActionMap; +/** #XrActionMap.flag */ +enum { + XR_ACTIONMAP_UPDATE = (1 << 0), +}; + +/* -------------------------------------------------------------------- */ + +typedef struct XrActionConfig { + struct XrActionConfig *next, *prev; + + /** Unique name. */ + char name[64]; /* MAX_NAME */ + + ListBase actionmaps; /* XrActionMap */ + short actactionmap; + short selactionmap; + short flag; + char _pad[2]; +} XrActionConfig; + +/** #XrActionConfig.flag */ +enum { + XR_ACTIONCONF_USER = (1 << 0), /* User action config. */ +}; + +/* -------------------------------------------------------------------- */ + +typedef struct XrMotionCaptureObject { + struct XrMotionCaptureObject *next, *prev; + + /** Object to bind to a VR device. Used as struct identifier. */ + Object *ob; + /** OpenXR user path, identifies the target headset/controller. */ + char user_path[64]; + + /** Location/rotation offsets. */ + float location_offset[3]; + float rotation_offset[3]; + + short flag; /* eXrMotionCaptureFlag */ + char _pad[6]; +} XrMotionCaptureObject; + /* -------------------------------------------------------------------- */ #ifdef __cplusplus diff --git a/source/blender/makesrna/intern/rna_screen.c b/source/blender/makesrna/intern/rna_screen.c index 912f47ba9aa..a8f28f6091c 100644 --- a/source/blender/makesrna/intern/rna_screen.c +++ b/source/blender/makesrna/intern/rna_screen.c @@ -46,6 +46,7 @@ const EnumPropertyItem rna_enum_region_type_items[] = { {RGN_TYPE_EXECUTE, "EXECUTE", 0, "Execute Buttons", ""}, {RGN_TYPE_FOOTER, "FOOTER", 0, "Footer", ""}, {RGN_TYPE_TOOL_HEADER, "TOOL_HEADER", 0, "Tool Header", ""}, + {RGN_TYPE_XR, "XR", 0, "XR", ""}, {0, NULL, 0, NULL, NULL}, }; diff --git a/source/blender/makesrna/intern/rna_ui_api.c b/source/blender/makesrna/intern/rna_ui_api.c index f96b3fc5eee..eea9fe89008 100644 --- a/source/blender/makesrna/intern/rna_ui_api.c +++ b/source/blender/makesrna/intern/rna_ui_api.c @@ -1755,6 +1755,11 @@ void RNA_api_ui_layout(StructRNA *srna) parm = RNA_def_pointer(func, "item", "KeyMapItem", "", ""); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); + func = RNA_def_function( + srna, "template_xr_actionmap_item_properties", "uiTemplateXrActionmapItemProperties"); + parm = RNA_def_pointer(func, "item", "XrActionMapItem", "", ""); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); + func = RNA_def_function(srna, "template_component_menu", "uiTemplateComponentMenu"); RNA_def_function_ui_description(func, "Item. Display expanded property in a popup menu"); parm = RNA_def_pointer(func, "data", "AnyType", "", "Data from which to take property"); diff --git a/source/blender/makesrna/intern/rna_wm.c b/source/blender/makesrna/intern/rna_wm.c index c2d1ac67675..107616c363f 100644 --- a/source/blender/makesrna/intern/rna_wm.c +++ b/source/blender/makesrna/intern/rna_wm.c @@ -362,6 +362,8 @@ const EnumPropertyItem rna_enum_event_type_items[] = { 0, "ActionZone Fullscreen", "AZone FullScr"}, + /* xr */ + {EVT_XR_ACTION, "XR_ACTION", 0, "XR Action", ""}, {0, NULL, 0, NULL, NULL}, }; @@ -700,6 +702,238 @@ static void rna_Event_tilt_get(PointerRNA *ptr, float *values) WM_event_tablet_data(event, NULL, values); } +static bool rna_Event_is_xr_get(PointerRNA *ptr) +{ + const wmEvent *event = ptr->data; + return WM_event_is_xr(event); +} + +static void rna_Event_xr_action_set_get(PointerRNA *ptr, char *value) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + WM_event_xr_data( + event, &value, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + } + else { + value[0] = '\0'; + } +} + +static int rna_Event_xr_action_set_length(PointerRNA *ptr) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + wmXrActionData *data = event->customdata; + return strlen(data->action_set) + 1; + } + else { + return 0; + } +} + +static void rna_Event_xr_action_get(PointerRNA *ptr, char *value) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + WM_event_xr_data( + event, NULL, &value, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + } + else { + value[0] = '\0'; + } +} + +static int rna_Event_xr_action_length(PointerRNA *ptr) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + wmXrActionData *data = event->customdata; + return strlen(data->action) + 1; + } + else { + return 0; + } +} + +static int rna_Event_xr_type_get(PointerRNA *ptr) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + int type; + WM_event_xr_data(event, + NULL, + NULL, + (char *)&type, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL); + return type; + } + else { + return 0; + } +} + +static void rna_Event_xr_state_get(PointerRNA *ptr, float *value) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + WM_event_xr_data( + event, NULL, NULL, NULL, value, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + } + else { + memset(value, 0, sizeof(float[2])); + } +} + +static void rna_Event_xr_state_other_get(PointerRNA *ptr, float *value) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + WM_event_xr_data( + event, NULL, NULL, NULL, NULL, value, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + } + else { + memset(value, 0, sizeof(float[2])); + } +} + +static float rna_Event_xr_float_threshold_get(PointerRNA *ptr) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + float float_threshold; + WM_event_xr_data(event, + NULL, + NULL, + NULL, + NULL, + NULL, + &float_threshold, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL); + return float_threshold; + } + else { + return 0.0f; + } +} + +static void rna_Event_xr_controller_location_get(PointerRNA *ptr, float *value) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + WM_event_xr_data( + event, NULL, NULL, NULL, NULL, NULL, NULL, value, NULL, NULL, NULL, NULL, NULL, NULL); + } + else { + memset(value, 0, sizeof(float[3])); + } +} + +static void rna_Event_xr_controller_rotation_get(PointerRNA *ptr, float *value) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + WM_event_xr_data( + event, NULL, NULL, NULL, NULL, NULL, NULL, NULL, value, NULL, NULL, NULL, NULL, NULL); + } + else { + value[0] = 1.0f; + value[1] = value[2] = value[3] = 0.0f; + } +} + +static void rna_Event_xr_controller_location_other_get(PointerRNA *ptr, float *value) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + WM_event_xr_data( + event, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, value, NULL, NULL, NULL, NULL); + } + else { + memset(value, 0, sizeof(float[3])); + } +} + +static void rna_Event_xr_controller_rotation_other_get(PointerRNA *ptr, float *value) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + WM_event_xr_data( + event, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, value, NULL, NULL, NULL); + } + else { + value[0] = 1.0f; + value[1] = value[2] = value[3] = 0.0f; + } +} + +static float rna_Event_xr_focal_length_get(PointerRNA *ptr) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + float focal_len; + WM_event_xr_data( + event, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &focal_len, NULL, NULL); + return focal_len; + } + else { + return 0.0f; + } +} + +static void rna_Event_xr_view_matrix_get(PointerRNA *ptr, float values[16]) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + WM_event_xr_data(event, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + (float(*)[4])values, + NULL); + } + else { + memset(values, 0, sizeof(float[16])); + } +} + +static bool rna_Event_xr_bimanual_get(PointerRNA *ptr) +{ + const wmEvent *event = ptr->data; + if (WM_event_is_xr(event)) { + bool bimanual; + WM_event_xr_data( + event, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &bimanual); + return bimanual; + } + else { + return false; + } +} + static PointerRNA rna_PopupMenu_layout_get(PointerRNA *ptr) { struct uiPopupMenu *pup = ptr->data; @@ -2130,6 +2364,13 @@ static void rna_def_event(BlenderRNA *brna) StructRNA *srna; PropertyRNA *prop; + static const EnumPropertyItem xr_action_types[] = { + {XR_BOOLEAN_INPUT, "BOOLEAN", 0, "Boolean", "Boolean value"}, + {XR_FLOAT_INPUT, "FLOAT", 0, "Float", "Float value"}, + {XR_VECTOR2F_INPUT, "VECTOR2F", 0, "Vector2f", "2D float vector value"}, + {0, NULL, 0, NULL, NULL}, + }; + srna = RNA_def_struct(brna, "Event", NULL); RNA_def_struct_ui_text(srna, "Event", "Window Manager Event"); RNA_def_struct_sdna(srna, "wmEvent"); @@ -2250,6 +2491,97 @@ static void rna_def_event(BlenderRNA *brna) RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_ui_text(prop, "OS Key", "True when the Cmd key is held"); + /* xr */ + prop = RNA_def_property(srna, "is_xr", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_boolean_funcs(prop, "rna_Event_is_xr_get", NULL); + RNA_def_property_ui_text(prop, "Is XR", "The event has XR data"); + + prop = RNA_def_property(srna, "xr_action_set", PROP_STRING, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_string_funcs( + prop, "rna_Event_xr_action_set_get", "rna_Event_xr_action_set_length", NULL); + RNA_def_property_ui_text(prop, "XR Action Set", "XR action set name"); + + prop = RNA_def_property(srna, "xr_action", PROP_STRING, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_string_funcs( + prop, "rna_Event_xr_action_get", "rna_Event_xr_action_length", NULL); + RNA_def_property_ui_text(prop, "XR Action", "XR action name"); + + prop = RNA_def_property(srna, "xr_type", PROP_ENUM, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_enum_items(prop, xr_action_types); + RNA_def_property_enum_funcs(prop, "rna_Event_xr_type_get", NULL, NULL); + RNA_def_property_ui_text(prop, "XR Type", "XR action type"); + + prop = RNA_def_property(srna, "xr_state", PROP_FLOAT, PROP_NONE); + RNA_def_property_array(prop, 2); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_float_funcs(prop, "rna_Event_xr_state_get", NULL, NULL); + RNA_def_property_ui_text(prop, "XR State", "XR action values corresponding to type"); + + prop = RNA_def_property(srna, "xr_state_other", PROP_FLOAT, PROP_NONE); + RNA_def_property_array(prop, 2); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_float_funcs(prop, "rna_Event_xr_state_other_get", NULL, NULL); + RNA_def_property_ui_text( + prop, "XR State Other", "State of the other user path for bimanual actions"); + + prop = RNA_def_property(srna, "xr_float_threshold", PROP_FLOAT, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_float_funcs(prop, "rna_Event_xr_float_threshold_get", NULL, NULL); + RNA_def_property_ui_text( + prop, "XR Float Threshold", "Input threshold for Float/Vector2f actions"); + + prop = RNA_def_property(srna, "xr_controller_location", PROP_FLOAT, PROP_TRANSLATION); + RNA_def_property_array(prop, 3); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_float_funcs(prop, "rna_Event_xr_controller_location_get", NULL, NULL); + RNA_def_property_ui_text(prop, + "XR Controller Location", + "Location of the action's corresponding controller aim in world space"); + + prop = RNA_def_property(srna, "xr_controller_rotation", PROP_FLOAT, PROP_QUATERNION); + RNA_def_property_array(prop, 4); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_float_funcs(prop, "rna_Event_xr_controller_rotation_get", NULL, NULL); + RNA_def_property_ui_text(prop, + "XR Controller Rotation", + "Rotation of the action's corresponding controller aim in world space"); + + prop = RNA_def_property(srna, "xr_controller_location_other", PROP_FLOAT, PROP_TRANSLATION); + RNA_def_property_array(prop, 3); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_float_funcs(prop, "rna_Event_xr_controller_location_other_get", NULL, NULL); + RNA_def_property_ui_text(prop, + "XR Controller Location Other", + "Controller aim location of the other user path for bimanual actions"); + + prop = RNA_def_property(srna, "xr_controller_rotation_other", PROP_FLOAT, PROP_QUATERNION); + RNA_def_property_array(prop, 4); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_float_funcs(prop, "rna_Event_xr_controller_rotation_other_get", NULL, NULL); + RNA_def_property_ui_text(prop, + "XR Controller Rotation Other", + "Controller aim rotation of the other user path for bimanual actions"); + + prop = RNA_def_property(srna, "xr_focal_length", PROP_FLOAT, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_float_funcs(prop, "rna_Event_xr_focal_length_get", NULL, NULL); + RNA_def_property_ui_text(prop, "XR Focal Length", "Focal length of the XR selection eye"); + + prop = RNA_def_property(srna, "xr_view_matrix", PROP_FLOAT, PROP_MATRIX); + RNA_def_property_array(prop, 16); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_float_funcs(prop, "rna_Event_xr_view_matrix_get", NULL, NULL); + RNA_def_property_ui_text(prop, "XR View Matrix", "View matrix of the XR selection eye"); + + prop = RNA_def_property(srna, "xr_bimanual", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_boolean_funcs(prop, "rna_Event_xr_bimanual_get", NULL); + RNA_def_property_ui_text(prop, "XR Bimanual", "Bimanual interaction is occurring"); + RNA_define_verify_sdna(1); /* not in sdna */ } diff --git a/source/blender/makesrna/intern/rna_xr.c b/source/blender/makesrna/intern/rna_xr.c index f24d28d1209..f64b6c5daca 100644 --- a/source/blender/makesrna/intern/rna_xr.c +++ b/source/blender/makesrna/intern/rna_xr.c @@ -40,24 +40,7 @@ # include "WM_api.h" /* -------------------------------------------------------------------- */ - -# ifdef WITH_XR_OPENXR -static wmXrData *rna_XrSession_wm_xr_data_get(PointerRNA *ptr) -{ - /* Callers could also get XrSessionState pointer through ptr->data, but prefer if we just - * consistently pass wmXrData pointers to the WM_xr_xxx() API. */ - - BLI_assert((ptr->type == &RNA_XrSessionSettings) || (ptr->type == &RNA_XrSessionState)); - - wmWindowManager *wm = (wmWindowManager *)ptr->owner_id; - BLI_assert(wm && (GS(wm->id.name) == ID_WM)); - - return &wm->xr; -} -# endif - -/* -------------------------------------------------------------------- */ -/** \name XR Action Map +/** \name XR Action Configuration * \{ */ static XrActionMapBinding *rna_XrActionMapBinding_new(XrActionMapItem *ami, @@ -171,15 +154,17 @@ static void rna_XrActionMapBinding_name_update(Main *bmain, Scene *UNUSED(scene) { # ifdef WITH_XR_OPENXR wmWindowManager *wm = bmain->wm.first; - if (wm && wm->xr.runtime) { - ListBase *actionmaps = WM_xr_actionmaps_get(wm->xr.runtime); - short idx = WM_xr_actionmap_selected_index_get(wm->xr.runtime); - XrActionMap *actionmap = BLI_findlink(actionmaps, idx); - if (actionmap) { - XrActionMapItem *ami = BLI_findlink(&actionmap->items, actionmap->selitem); - if (ami) { - XrActionMapBinding *amb = ptr->data; - WM_xr_actionmap_binding_ensure_unique(ami, amb); + if (wm) { + XrSessionSettings *settings = &wm->xr.session_settings; + XrActionConfig *actionconf = WM_xr_actionconfig_active_get(settings); + if (actionconf) { + XrActionMap *actionmap = BLI_findlink(&actionconf->actionmaps, actionconf->selactionmap); + if (actionmap) { + XrActionMapItem *ami = BLI_findlink(&actionmap->items, actionmap->selitem); + if (ami) { + XrActionMapBinding *amb = ptr->data; + WM_xr_actionmap_binding_ensure_unique(ami, amb); + } } } } @@ -413,13 +398,15 @@ static void rna_XrActionMapItem_name_update(Main *bmain, Scene *UNUSED(scene), P { # ifdef WITH_XR_OPENXR wmWindowManager *wm = bmain->wm.first; - if (wm && wm->xr.runtime) { - ListBase *actionmaps = WM_xr_actionmaps_get(wm->xr.runtime); - short idx = WM_xr_actionmap_selected_index_get(wm->xr.runtime); - XrActionMap *actionmap = BLI_findlink(actionmaps, idx); - if (actionmap) { - XrActionMapItem *ami = ptr->data; - WM_xr_actionmap_item_ensure_unique(actionmap, ami); + if (wm) { + XrSessionSettings *settings = &wm->xr.session_settings; + XrActionConfig *actionconf = WM_xr_actionconfig_active_get(settings); + if (actionconf) { + XrActionMap *actionmap = BLI_findlink(&actionconf->actionmaps, actionconf->selactionmap); + if (actionmap) { + XrActionMapItem *ami = ptr->data; + WM_xr_actionmap_item_ensure_unique(actionmap, ami); + } } } # else @@ -437,50 +424,51 @@ static void rna_XrActionMapItem_update(Main *UNUSED(bmain), Scene *UNUSED(scene) # endif } -static XrActionMap *rna_XrActionMap_new(PointerRNA *ptr, const char *name, bool replace_existing) +static XrActionMap *rna_XrActionMap_new(XrActionConfig *actionconf, + const char *name, + bool replace_existing) { # ifdef WITH_XR_OPENXR - wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr); - return WM_xr_actionmap_new(xr->runtime, name, replace_existing); + return WM_xr_actionmap_new(actionconf, name, replace_existing); # else - UNUSED_VARS(ptr, name, replace_existing); + UNUSED_VARS(actionconf, name, replace_existing); return NULL; # endif } -static XrActionMap *rna_XrActionMap_new_from_actionmap(PointerRNA *ptr, XrActionMap *am_src) +static XrActionMap *rna_XrActionMap_new_from_actionmap(XrActionConfig *actionconf, + XrActionMap *am_src) { # ifdef WITH_XR_OPENXR - wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr); - return WM_xr_actionmap_add_copy(xr->runtime, am_src); + return WM_xr_actionmap_add_copy(actionconf, am_src); # else - UNUSED_VARS(ptr, am_src); + UNUSED_VARS(actionconf, am_src); return NULL; # endif } -static void rna_XrActionMap_remove(ReportList *reports, PointerRNA *ptr, PointerRNA *actionmap_ptr) +static void rna_XrActionMap_remove(XrActionConfig *actionconf, + ReportList *reports, + PointerRNA *actionmap_ptr) { # ifdef WITH_XR_OPENXR - wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr); XrActionMap *actionmap = actionmap_ptr->data; - if (WM_xr_actionmap_remove(xr->runtime, actionmap) == false) { + if (WM_xr_actionmap_remove(actionconf, actionmap) == false) { BKE_reportf(reports, RPT_ERROR, "ActionMap '%s' cannot be removed", actionmap->name); return; } RNA_POINTER_INVALIDATE(actionmap_ptr); # else - UNUSED_VARS(ptr, reports, actionmap_ptr); + UNUSED_VARS(actionconf, reports, actionmap_ptr); # endif } -static XrActionMap *rna_XrActionMap_find(PointerRNA *ptr, const char *name) +static XrActionMap *rna_XrActionMap_find(XrActionConfig *actionconf, const char *name) { # ifdef WITH_XR_OPENXR - wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr); - return WM_xr_actionmap_find(xr->runtime, name); + return WM_xr_actionmap_find(actionconf, name); # else - UNUSED_VARS(ptr, name); + UNUSED_VARS(actionconf, name); return NULL; # endif } @@ -489,18 +477,224 @@ static void rna_XrActionMap_name_update(Main *bmain, Scene *UNUSED(scene), Point { # ifdef WITH_XR_OPENXR wmWindowManager *wm = bmain->wm.first; - if (wm && wm->xr.runtime) { - XrActionMap *actionmap = ptr->data; - WM_xr_actionmap_ensure_unique(wm->xr.runtime, actionmap); + if (wm) { + XrSessionSettings *settings = &wm->xr.session_settings; + XrActionConfig *actionconf = WM_xr_actionconfig_active_get(settings); + if (actionconf) { + XrActionMap *actionmap = ptr->data; + WM_xr_actionmap_ensure_unique(actionconf, actionmap); + } } # else UNUSED_VARS(bmain, ptr); # endif } +static XrActionConfig *rna_XrActionConfig_new(XrSessionSettings *settings, const char *name) +{ +# ifdef WITH_XR_OPENXR + return WM_xr_actionconfig_new(settings, name, true); +# else + UNUSED_VARS(settings, name); + return NULL; +# endif +} + +static void rna_XrActionConfig_remove(XrSessionSettings *settings, + ReportList *reports, + PointerRNA *actionconf_ptr) +{ +# ifdef WITH_XR_OPENXR + XrActionConfig *actionconf = actionconf_ptr->data; + if (WM_xr_actionconfig_remove(settings, actionconf) == false) { + BKE_reportf(reports, RPT_ERROR, "ActionConfig '%s' cannot be removed", actionconf->name); + return; + } + RNA_POINTER_INVALIDATE(actionconf_ptr); +# else + UNUSED_VARS(settings, reports, actionconf_ptr); +# endif +} + +static void rna_XrActionConfig_update(XrSessionSettings *settings) +{ +# ifdef WITH_XR_OPENXR + WM_xr_actionconfig_update(settings); +# else + UNUSED_VARS(settings); +# endif +} + +static PointerRNA rna_XrSessionSettings_actionconfig_active_get(PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + XrSessionSettings *settings = ptr->data; + XrActionConfig *ac = WM_xr_actionconfig_active_get(settings); + if (!ac) { + ac = settings->defaultconf; + } + return rna_pointer_inherit_refine(ptr, &RNA_XrActionConfig, ac); +# else + UNUSED_VARS(ptr); + return PointerRNA_NULL; +# endif +} + +static void rna_XrSessionSettings_actionconfig_active_set(PointerRNA *ptr, + PointerRNA value, + struct ReportList *UNUSED(reports)) +{ +# ifdef WITH_XR_OPENXR + XrSessionSettings *settings = ptr->data; + XrActionConfig *ac = value.data; + if (ac) { + WM_xr_actionconfig_active_set(settings, ac->name); + } +# else + UNUSED_VARS(ptr, value); +# endif +} + /** \} */ /* -------------------------------------------------------------------- */ +/** \name XR Motion Capture + * \{ */ + +static XrMotionCaptureObject *rna_XrMotionCaptureObject_new(XrSessionSettings *settings, + PointerRNA *ob_ptr) +{ +# ifdef WITH_XR_OPENXR + Object *ob = ob_ptr->data; + return WM_xr_mocap_object_new(settings, ob); +# else + UNUSED_VARS(settings, ob_ptr); + return NULL; +# endif +} + +static void rna_XrMotionCaptureObject_remove(XrSessionSettings *settings, PointerRNA *mocap_ob_ptr) +{ +# ifdef WITH_XR_OPENXR + XrMotionCaptureObject *mocap_ob = mocap_ob_ptr->data; + WM_xr_mocap_object_remove(settings, mocap_ob); + RNA_POINTER_INVALIDATE(mocap_ob_ptr); +# else + UNUSED_VARS(settings, mocap_ob_ptr); +# endif +} + +static XrMotionCaptureObject *rna_XrMotionCaptureObject_find(XrSessionSettings *settings, + PointerRNA *ob_ptr) +{ +# ifdef WITH_XR_OPENXR + Object *ob = ob_ptr->data; + return WM_xr_mocap_object_find(settings, ob); +# else + UNUSED_VARS(settings, ob_ptr); + return NULL; +# endif +} + +static void rna_XrMotionCaptureObject_object_update(Main *bmain, + Scene *UNUSED(scene), + PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + wmWindowManager *wm = bmain->wm.first; + if (wm) { + XrMotionCaptureObject *mocap_ob = ptr->data; + WM_xr_mocap_object_ensure_unique(&wm->xr.session_settings, mocap_ob); + } +# else + UNUSED_VARS(bmain, ptr); +# endif +} + +static bool rna_XrMotionCaptureObject_enable_get(PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + const XrMotionCaptureObject *mocap_ob = ptr->data; + return (mocap_ob->flag & XR_MOCAP_OBJECT_ENABLE) != 0; +# else + UNUSED_VARS(ptr); + return false; +# endif +} + +static void rna_XrMotionCaptureObject_enable_set(PointerRNA *ptr, bool value) +{ +# ifdef WITH_XR_OPENXR + XrMotionCaptureObject *mocap_ob = ptr->data; + SET_FLAG_FROM_TEST(mocap_ob->flag, value, XR_MOCAP_OBJECT_ENABLE); +# else + UNUSED_VARS(ptr, value); +# endif +} + +static void rna_XrMotionCaptureObject_enable_update(Main *bmain, + Scene *UNUSED(scene), + PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + wmWindowManager *wm = bmain->wm.first; + if (wm) { + XrMotionCaptureObject *mocap_ob = ptr->data; + if ((mocap_ob->flag & XR_MOCAP_OBJECT_ENABLE) != 0) { + /* Store object's original pose. */ + WM_xr_session_state_mocap_pose_set(&wm->xr, mocap_ob); + } + else { + /* Restore object's original pose. */ + WM_xr_session_state_mocap_pose_get(&wm->xr, mocap_ob); + } + } +# else + UNUSED_VARS(bmain, ptr); +# endif +} + +static bool rna_XrMotionCaptureObject_autokey_get(PointerRNA *ptr) +{ +# ifdef WITH_XR_OPENXR + const XrMotionCaptureObject *mocap_ob = ptr->data; + return (mocap_ob->flag & XR_MOCAP_OBJECT_AUTOKEY) != 0; +# else + UNUSED_VARS(ptr); + return false; +# endif +} + +static void rna_XrMotionCaptureObject_autokey_set(PointerRNA *ptr, bool value) +{ +# ifdef WITH_XR_OPENXR + XrMotionCaptureObject *mocap_ob = ptr->data; + SET_FLAG_FROM_TEST(mocap_ob->flag, value, XR_MOCAP_OBJECT_AUTOKEY); +# else + UNUSED_VARS(ptr, value); +# endif +} + +/** \} */ + +/* -------------------------------------------------------------------- */ + +# ifdef WITH_XR_OPENXR +static wmXrData *rna_XrSession_wm_xr_data_get(PointerRNA *ptr) +{ + /* Callers could also get XrSessionState pointer through ptr->data, but prefer if we just + * consistently pass wmXrData pointers to the WM_xr_xxx() API. */ + + BLI_assert((ptr->type == &RNA_XrSessionSettings) || (ptr->type == &RNA_XrSessionState)); + + wmWindowManager *wm = (wmWindowManager *)ptr->owner_id; + BLI_assert(wm && (GS(wm->id.name) == ID_WM)); + + return &wm->xr; +} +# endif + +/* -------------------------------------------------------------------- */ /** \name XR Session Settings * \{ */ @@ -892,54 +1086,66 @@ static void rna_XrSessionState_viewer_pose_rotation_get(PointerRNA *ptr, float * # endif } -static void rna_XrSessionState_actionmaps_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +static void rna_XrSessionState_nav_location_get(PointerRNA *ptr, float *r_values) +{ +# ifdef WITH_XR_OPENXR + const wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr); + WM_xr_session_state_nav_location_get(xr, r_values); +# else + UNUSED_VARS(ptr); + zero_v3(r_values); +# endif +} + +static void rna_XrSessionState_nav_location_set(PointerRNA *ptr, const float *values) { # ifdef WITH_XR_OPENXR wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr); - ListBase *lb = WM_xr_actionmaps_get(xr->runtime); - rna_iterator_listbase_begin(iter, lb, NULL); + WM_xr_session_state_nav_location_set(xr, values); # else - UNUSED_VARS(iter, ptr); + UNUSED_VARS(ptr, values); # endif } -static int rna_XrSessionState_active_actionmap_get(PointerRNA *ptr) +static void rna_XrSessionState_nav_rotation_get(PointerRNA *ptr, float *r_values) { # ifdef WITH_XR_OPENXR const wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr); - return WM_xr_actionmap_active_index_get(xr->runtime); + WM_xr_session_state_nav_rotation_get(xr, r_values); # else UNUSED_VARS(ptr); - return -1; + unit_qt(r_values); # endif } -static void rna_XrSessionState_active_actionmap_set(PointerRNA *ptr, int value) +static void rna_XrSessionState_nav_rotation_set(PointerRNA *ptr, const float *values) { # ifdef WITH_XR_OPENXR wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr); - WM_xr_actionmap_active_index_set(xr->runtime, (short)value); + WM_xr_session_state_nav_rotation_set(xr, values); # else - UNUSED_VARS(ptr, value); + UNUSED_VARS(ptr, values); # endif } -static int rna_XrSessionState_selected_actionmap_get(PointerRNA *ptr) +static float rna_XrSessionState_nav_scale_get(PointerRNA *ptr) { + float value; # ifdef WITH_XR_OPENXR const wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr); - return WM_xr_actionmap_selected_index_get(xr->runtime); + WM_xr_session_state_nav_scale_get(xr, &value); # else UNUSED_VARS(ptr); - return -1; + value = 1.0f; # endif + return value; } -static void rna_XrSessionState_selected_actionmap_set(PointerRNA *ptr, int value) +static void rna_XrSessionState_nav_scale_set(PointerRNA *ptr, float value) { # ifdef WITH_XR_OPENXR wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr); - WM_xr_actionmap_selected_index_set(xr->runtime, (short)value); + WM_xr_session_state_nav_scale_set(xr, value); # else UNUSED_VARS(ptr, value); # endif @@ -1037,7 +1243,7 @@ static const EnumPropertyItem rna_enum_xr_axis1_flags[] = { }; /* -------------------------------------------------------------------- */ -/** \name XR Action Map +/** \name XR Action Configuration * \{ */ static void rna_def_xr_actionmap_bindings(BlenderRNA *brna, PropertyRNA *cprop) @@ -1058,7 +1264,7 @@ static void rna_def_xr_actionmap_bindings(BlenderRNA *brna, PropertyRNA *cprop) "replace_existing", true, "Replace Existing", - "Replace any existing binding with the same name"); + "Replace any existing binding with same name"); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); parm = RNA_def_pointer( func, "binding", "XrActionMapBinding", "Binding", "Added action map binding"); @@ -1107,7 +1313,7 @@ static void rna_def_xr_actionmap_items(BlenderRNA *brna, PropertyRNA *cprop) "replace_existing", true, "Replace Existing", - "Replace any existing item with the same name"); + "Replace any existing item with same name"); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); parm = RNA_def_pointer(func, "item", "XrActionMapItem", "Item", "Added action map item"); RNA_def_function_return(func, parm); @@ -1140,27 +1346,22 @@ static void rna_def_xr_actionmaps(BlenderRNA *brna, PropertyRNA *cprop) RNA_def_property_srna(cprop, "XrActionMaps"); srna = RNA_def_struct(brna, "XrActionMaps", NULL); + RNA_def_struct_sdna(srna, "XrActionConfig"); RNA_def_struct_ui_text(srna, "XR Action Maps", "Collection of XR action maps"); func = RNA_def_function(srna, "new", "rna_XrActionMap_new"); - RNA_def_function_flag(func, FUNC_NO_SELF); - parm = RNA_def_pointer(func, "xr_session_state", "XrSessionState", "XR Session State", ""); - RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); parm = RNA_def_string(func, "name", NULL, MAX_NAME, "Name", ""); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); parm = RNA_def_boolean(func, "replace_existing", true, "Replace Existing", - "Replace any existing actionmap with the same name"); + "Replace any existing actionmap with same name"); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); parm = RNA_def_pointer(func, "actionmap", "XrActionMap", "Action Map", "Added action map"); RNA_def_function_return(func, parm); func = RNA_def_function(srna, "new_from_actionmap", "rna_XrActionMap_new_from_actionmap"); - RNA_def_function_flag(func, FUNC_NO_SELF); - parm = RNA_def_pointer(func, "xr_session_state", "XrSessionState", "XR Session State", ""); - RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); parm = RNA_def_pointer( func, "actionmap", "XrActionMap", "Action Map", "Action map to use as a reference"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); @@ -1168,17 +1369,12 @@ static void rna_def_xr_actionmaps(BlenderRNA *brna, PropertyRNA *cprop) RNA_def_function_return(func, parm); func = RNA_def_function(srna, "remove", "rna_XrActionMap_remove"); - RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_USE_REPORTS); - parm = RNA_def_pointer(func, "xr_session_state", "XrSessionState", "XR Session State", ""); - RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); + RNA_def_function_flag(func, FUNC_USE_REPORTS); parm = RNA_def_pointer(func, "actionmap", "XrActionMap", "Action Map", "Removed action map"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, 0); func = RNA_def_function(srna, "find", "rna_XrActionMap_find"); - RNA_def_function_flag(func, FUNC_NO_SELF); - parm = RNA_def_pointer(func, "xr_session_state", "XrSessionState", "XR Session State", ""); - RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); parm = RNA_def_string(func, "name", NULL, MAX_NAME, "Name", ""); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); parm = RNA_def_pointer( @@ -1186,11 +1382,106 @@ static void rna_def_xr_actionmaps(BlenderRNA *brna, PropertyRNA *cprop) RNA_def_function_return(func, parm); } -static void rna_def_xr_actionmap(BlenderRNA *brna) +static void rna_def_xr_actionconfigs(BlenderRNA *brna, PropertyRNA *cprop) +{ + StructRNA *srna; + FunctionRNA *func; + PropertyRNA *prop, *parm; + + RNA_def_property_srna(cprop, "XrActionConfigurations"); + srna = RNA_def_struct(brna, "XrActionConfigurations", NULL); + RNA_def_struct_sdna(srna, "XrSessionSettings"); + RNA_def_struct_ui_text( + srna, "XR Action Configurations", "Collection of XR action configurations"); + + prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "XrActionConfig"); + RNA_def_property_pointer_funcs(prop, + "rna_XrSessionSettings_actionconfig_active_get", + "rna_XrSessionSettings_actionconfig_active_set", + NULL, + NULL); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text( + prop, "Active XR Action Configuration", "Active XR action configuration (preset)"); + + prop = RNA_def_property(srna, "default", PROP_POINTER, PROP_NEVER_NULL); + RNA_def_property_pointer_sdna(prop, NULL, "defaultconf"); + RNA_def_property_struct_type(prop, "XrActionConfig"); + RNA_def_property_ui_text( + prop, "Default XR Action Configuration", "Default builtin XR action configuration"); + + prop = RNA_def_property(srna, "addon", PROP_POINTER, PROP_NEVER_NULL); + RNA_def_property_pointer_sdna(prop, NULL, "addonconf"); + RNA_def_property_struct_type(prop, "XrActionConfig"); + RNA_def_property_ui_text(prop, + "Add-on XR Action Configuration", + "XR action configuration that can be extended by add-ons"); + + prop = RNA_def_property(srna, "user", PROP_POINTER, PROP_NEVER_NULL); + RNA_def_property_pointer_sdna(prop, NULL, "userconf"); + RNA_def_property_struct_type(prop, "XrActionConfig"); + RNA_def_property_ui_text(prop, + "User XR Action Configuration", + "XR action configuration that can be extended by users"); + + func = RNA_def_function(srna, "new", "rna_XrActionConfig_new"); + parm = RNA_def_string(func, "name", NULL, MAX_NAME, "Name", ""); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_pointer(func, + "actionconfig", + "XrActionConfig", + "Action Configuration", + "Added action configuration"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "remove", "rna_XrActionConfig_remove"); + RNA_def_function_flag(func, FUNC_USE_REPORTS); + parm = RNA_def_pointer(func, + "actionconfig", + "XrActionConfig", + "Action Configuration", + "Removed action configuration"); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); + RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, 0); + + RNA_def_function(srna, "update", "rna_XrActionConfig_update"); +} + +static void rna_def_xr_actionconfig(BlenderRNA *brna) { StructRNA *srna; PropertyRNA *prop; + /* XrActionConfig */ + srna = RNA_def_struct(brna, "XrActionConfig", NULL); + RNA_def_struct_sdna(srna, "XrActionConfig"); + RNA_def_struct_ui_text(srna, "XR Action Configuration", ""); + + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_ui_text(prop, "Name", "Name of the action configuration"); + RNA_def_struct_name_property(srna, prop); + + prop = RNA_def_property(srna, "actionmaps", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "XrActionMap"); + RNA_def_property_ui_text( + prop, "Action Maps", "Action maps configured as part of this configuration"); + rna_def_xr_actionmaps(brna, prop); + + prop = RNA_def_property(srna, "active_actionmap", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "actactionmap"); + RNA_def_property_ui_text(prop, "Active Action Map", ""); + + prop = RNA_def_property(srna, "selected_actionmap", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "selactionmap"); + RNA_def_property_ui_text(prop, "Selected Action Map", ""); + + prop = RNA_def_property(srna, "is_user_defined", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", KEYCONF_USER); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text( + prop, "User Defined", "Indicates that an action configuration was defined by the user"); + /* XrActionMap */ srna = RNA_def_struct(brna, "XrActionMap", NULL); RNA_def_struct_sdna(srna, "XrActionMap"); @@ -1321,7 +1612,7 @@ static void rna_def_xr_actionmap(BlenderRNA *brna) prop = RNA_def_property(srna, "bindings", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "XrActionMapBinding"); RNA_def_property_ui_text( - prop, "Bindings", "Bindings for the action map item, mapping the action to an XR input"); + prop, "Bindings", "Bindings in the actionmap, linking an action to an XR input"); rna_def_xr_actionmap_bindings(brna, prop); prop = RNA_def_property(srna, "selected_binding", PROP_INT, PROP_NONE); @@ -1331,7 +1622,9 @@ static void rna_def_xr_actionmap(BlenderRNA *brna) /* XrActionMapBinding */ srna = RNA_def_struct(brna, "XrActionMapBinding", NULL); RNA_def_struct_sdna(srna, "XrActionMapBinding"); - RNA_def_struct_ui_text(srna, "XR Action Map Binding", "Binding in an XR action map item"); + RNA_def_struct_ui_text(srna, + "XR Action Map Binding", + "Bindings for the action map item, mapping the action to an XR input"); prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); RNA_def_property_ui_text(prop, "Name", "Name of the action map binding"); @@ -1353,7 +1646,7 @@ static void rna_def_xr_actionmap(BlenderRNA *brna) prop = RNA_def_property(srna, "threshold", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "float_threshold"); RNA_def_property_range(prop, 0.0, 1.0); - RNA_def_property_ui_text(prop, "Threshold", "Input threshold for button/axis actions"); + RNA_def_property_ui_text(prop, "Input Threshold", "Input threshold for button/axis actions"); prop = RNA_def_property(srna, "axis0_region", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_enum_xr_axis0_flags); @@ -1374,10 +1667,101 @@ static void rna_def_xr_actionmap(BlenderRNA *brna) prop, "Axis 1 Region", "Action execution region for the second input axis"); prop = RNA_def_property(srna, "pose_location", PROP_FLOAT, PROP_TRANSLATION); - RNA_def_property_ui_text(prop, "Pose Location Offset", ""); + RNA_def_property_ui_text(prop, "Pose Location Offset", "Pose location offset"); prop = RNA_def_property(srna, "pose_rotation", PROP_FLOAT, PROP_EULER); - RNA_def_property_ui_text(prop, "Pose Rotation Offset", ""); + RNA_def_property_ui_text(prop, "Pose Rotation Offset", "Pose rotation offset"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name XR Motion Capture + * \{ */ + +static void rna_def_xr_motioncapture_objects(BlenderRNA *brna, PropertyRNA *cprop) +{ + StructRNA *srna; + FunctionRNA *func; + PropertyRNA *parm; + + RNA_def_property_srna(cprop, "XrMotionCaptureObjects"); + srna = RNA_def_struct(brna, "XrMotionCaptureObjects", NULL); + RNA_def_struct_sdna(srna, "XrSessionSettings"); + RNA_def_struct_ui_text( + srna, "XR Motion Capture Objects", "Collection of XR motion capture objects"); + + func = RNA_def_function(srna, "new", "rna_XrMotionCaptureObject_new"); + parm = RNA_def_pointer(func, + "object", + "Object", + "Object", + "Blender object used to identify the motion capture object"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED | PARM_RNAPTR); + parm = RNA_def_pointer(func, + "mocap_object", + "XrMotionCaptureObject", + "Motion Capture Object", + "Added motion capture object"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "remove", "rna_XrMotionCaptureObject_remove"); + parm = RNA_def_pointer(func, + "mocap_object", + "XrMotionCaptureObject", + "Motion Capture Object", + "Removed motion capture object"); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); + RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, 0); + + func = RNA_def_function(srna, "find", "rna_XrMotionCaptureObject_find"); + parm = RNA_def_pointer( + func, "object", "Object", "Object", "Blender object identifying the motion capture object"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED | PARM_RNAPTR); + parm = RNA_def_pointer(func, + "mocap_object", + "XrMotionCaptureObject", + "Motion Capture Object", + "The motion capture object with the given name"); + RNA_def_function_return(func, parm); +} + +static void rna_def_xr_motioncapture_object(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "XrMotionCaptureObject", NULL); + RNA_def_struct_sdna(srna, "XrMotionCaptureObject"); + RNA_def_struct_ui_text(srna, "XR Motion Capture Object", ""); + + prop = RNA_def_property(srna, "object", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "ob"); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Object", "Object to bind to a VR device"); + RNA_def_property_update(prop, 0, "rna_XrMotionCaptureObject_object_update"); + + prop = RNA_def_property(srna, "user_path", PROP_STRING, PROP_NONE); + RNA_def_property_string_maxlength(prop, 64); + RNA_def_property_ui_text(prop, "User Path", "OpenXR user path identifying the target VR device"); + + prop = RNA_def_property(srna, "location_offset", PROP_FLOAT, PROP_TRANSLATION); + RNA_def_property_ui_text(prop, "Location Offset", "Location offset in device space"); + + prop = RNA_def_property(srna, "rotation_offset", PROP_FLOAT, PROP_EULER); + RNA_def_property_ui_text(prop, "Rotation Offset", "Rotation offset in device space"); + + prop = RNA_def_property(srna, "enable", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_boolean_funcs( + prop, "rna_XrMotionCaptureObject_enable_get", "rna_XrMotionCaptureObject_enable_set"); + RNA_def_property_ui_text(prop, "Enable", "Bind object to target VR device"); + RNA_def_property_update(prop, 0, "rna_XrMotionCaptureObject_enable_update"); + + prop = RNA_def_property(srna, "autokey", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_funcs( + prop, "rna_XrMotionCaptureObject_autokey_get", "rna_XrMotionCaptureObject_autokey_set"); + RNA_def_property_ui_text(prop, "Auto Key", "Auto-insert keyframes on animation playback"); } /** \} */ @@ -1410,6 +1794,32 @@ static void rna_def_xr_session_settings(BlenderRNA *brna) {0, NULL, 0, NULL, NULL}, }; + static const EnumPropertyItem controller_draw_styles[] = { + {XR_CONTROLLER_DRAW_DARK, "DARK", 0, "Dark", "Draw dark controller"}, + {XR_CONTROLLER_DRAW_LIGHT, "LIGHT", 0, "Light", "Draw light controller"}, + {XR_CONTROLLER_DRAW_DARK_RAY, + "DARK_RAY", + 0, + "Dark + Ray", + "Draw dark controller with aiming axis ray"}, + {XR_CONTROLLER_DRAW_LIGHT_RAY, + "LIGHT_RAY", + 0, + "Light + Ray", + "Draw light controller with aiming axis ray"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem selection_eyes[] = { + {XR_EYE_LEFT, "EYE_LEFT", 0, "Left Eye", "Use the left eye's perspective for VR selection"}, + {XR_EYE_RIGHT, + "EYE_RIGHT", + 0, + "Right Eye", + "Use the right eye's perspective for VR selection"}, + {0, NULL, 0, NULL, NULL}, + }; + srna = RNA_def_struct(brna, "XrSessionSettings", NULL); RNA_def_struct_ui_text(srna, "XR Session Settings", ""); @@ -1450,6 +1860,13 @@ static void rna_def_xr_session_settings(BlenderRNA *brna) "Rotation angle around the Z-Axis to apply the rotation deltas from the VR headset to"); RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL); + prop = RNA_def_property(srna, "base_scale", PROP_FLOAT, PROP_NONE); + RNA_def_property_ui_text(prop, "Base Scale", "Uniform scale to apply to VR view"); + RNA_def_property_range(prop, 0.001f, 1000.0f); + RNA_def_property_ui_range(prop, 0.001f, 1000.0f, 1, 3); + RNA_def_property_float_default(prop, 1.0f); + RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL); + prop = RNA_def_property(srna, "show_floor", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "draw_flags", V3D_OFSDRAW_SHOW_GRIDFLOOR); RNA_def_property_ui_text(prop, "Display Grid Floor", "Show the ground plane grid"); @@ -1460,6 +1877,36 @@ static void rna_def_xr_session_settings(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Show Annotation", "Show annotations for this view"); RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL); + prop = RNA_def_property(srna, "show_selection", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "draw_flags", V3D_OFSDRAW_SHOW_SELECTION); + RNA_def_property_ui_text(prop, "Show Selection", "Show selection outlines"); + RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL); + + prop = RNA_def_property(srna, "show_controllers", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "draw_flags", V3D_OFSDRAW_XR_SHOW_CONTROLLERS); + RNA_def_property_ui_text( + prop, "Show Controllers", "Show VR controllers (requires VR actions for controller poses)"); + RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL); + + prop = RNA_def_property(srna, "show_custom_overlays", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "draw_flags", V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS); + RNA_def_property_ui_text(prop, "Show Custom Overlays", "Show custom VR overlays"); + RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL); + + prop = RNA_def_property(srna, "controller_draw_style", PROP_ENUM, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_enum_items(prop, controller_draw_styles); + RNA_def_property_ui_text( + 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, "selection_eye", PROP_ENUM, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_enum_items(prop, selection_eyes); + RNA_def_property_ui_text( + prop, "Selection Eye", "Which eye's perspective to use when selecting in VR (GPU select)"); + 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); @@ -1489,6 +1936,23 @@ static void rna_def_xr_session_settings(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Absolute Tracking", "Use unadjusted location/rotation as defined by the XR runtime"); RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL); + + prop = RNA_def_property(srna, "actionconfigs", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "XrActionConfig"); + RNA_def_property_ui_text( + prop, "XR Action Configurations", "Registered XR action configurations"); + rna_def_xr_actionconfigs(brna, prop); + + prop = RNA_def_property(srna, "mocap_objects", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "XrMotionCaptureObject"); + RNA_def_property_ui_text( + prop, "XR Motion Capture Objects", "Objects to bind to headset/controller poses"); + rna_def_xr_motioncapture_objects(brna, prop); + + prop = RNA_def_property(srna, "selected_mocap_object", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "sel_mocap_object"); + RNA_def_property_ui_text( + prop, "Selected Motion Capture Object", "Currently selected motion capture object"); } /** \} */ @@ -1793,33 +2257,30 @@ static void rna_def_xr_session_state(BlenderRNA *brna) "Viewer Pose Rotation", "Last known rotation of the viewer pose (center between the eyes) in world space"); - prop = RNA_def_property(srna, "actionmaps", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_funcs(prop, - "rna_XrSessionState_actionmaps_begin", - "rna_iterator_listbase_next", - "rna_iterator_listbase_end", - "rna_iterator_listbase_get", - NULL, - NULL, - NULL, - NULL); - RNA_def_property_struct_type(prop, "XrActionMap"); - RNA_def_property_ui_text(prop, "XR Action Maps", ""); - rna_def_xr_actionmaps(brna, prop); + prop = RNA_def_property(srna, "navigation_location", PROP_FLOAT, PROP_TRANSLATION); + RNA_def_property_array(prop, 3); + RNA_def_property_float_funcs( + prop, "rna_XrSessionState_nav_location_get", "rna_XrSessionState_nav_location_set", NULL); + RNA_def_property_ui_text( + prop, + "Navigation Location", + "Location offset to apply to base pose when determining viewer location"); - prop = RNA_def_property(srna, "active_actionmap", PROP_INT, PROP_NONE); - RNA_def_property_int_funcs(prop, - "rna_XrSessionState_active_actionmap_get", - "rna_XrSessionState_active_actionmap_set", - NULL); - RNA_def_property_ui_text(prop, "Active Action Map", ""); + prop = RNA_def_property(srna, "navigation_rotation", PROP_FLOAT, PROP_QUATERNION); + RNA_def_property_array(prop, 4); + RNA_def_property_float_funcs( + prop, "rna_XrSessionState_nav_rotation_get", "rna_XrSessionState_nav_rotation_set", NULL); + RNA_def_property_ui_text( + prop, + "Navigation Rotation", + "Rotation offset to apply to base pose when determining viewer rotation"); - prop = RNA_def_property(srna, "selected_actionmap", PROP_INT, PROP_NONE); - RNA_def_property_int_funcs(prop, - "rna_XrSessionState_selected_actionmap_get", - "rna_XrSessionState_selected_actionmap_set", - NULL); - RNA_def_property_ui_text(prop, "Selected Action Map", ""); + prop = RNA_def_property(srna, "navigation_scale", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_funcs( + prop, "rna_XrSessionState_nav_scale_get", "rna_XrSessionState_nav_scale_set", NULL); + RNA_def_property_ui_text(prop, + "Navigation Scale", + "Scale multiplier to apply to base pose when determining viewer scale"); } /** \} */ @@ -1828,7 +2289,8 @@ void RNA_def_xr(BlenderRNA *brna) { RNA_define_animate_sdna(false); - rna_def_xr_actionmap(brna); + rna_def_xr_actionconfig(brna); + rna_def_xr_motioncapture_object(brna); rna_def_xr_session_settings(brna); rna_def_xr_session_state(brna); diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index 4d65726fe2b..0a13eeec6db 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -195,6 +195,8 @@ if(WITH_XR_OPENXR) list(APPEND INC xr + # for wm_xr_operators.c: BKE_editmesh.h + ../bmesh ) list(APPEND SRC @@ -202,6 +204,8 @@ if(WITH_XR_OPENXR) xr/intern/wm_xr_action.c xr/intern/wm_xr_actionmap.c xr/intern/wm_xr_draw.c + xr/intern/wm_xr_mocap.c + xr/intern/wm_xr_operators.c xr/intern/wm_xr_session.c xr/wm_xr.h diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 577561017c4..cf90179f129 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -73,10 +73,6 @@ struct wmTabletData; struct wmNDOFMotionData; #endif -#ifdef WITH_XR_OPENXR -struct wmXrRuntimeData; -#endif - typedef struct wmGizmo wmGizmo; typedef struct wmGizmoMap wmGizmoMap; typedef struct wmGizmoMapType wmGizmoMapType; @@ -664,7 +660,13 @@ void WM_paneltype_remove(struct PanelType *pt); /* wm_gesture_ops.c */ int WM_gesture_box_invoke(struct bContext *C, struct wmOperator *op, const struct wmEvent *event); +int WM_gesture_box_invoke_3d(struct bContext *C, + struct wmOperator *op, + const struct wmEvent *event); int WM_gesture_box_modal(struct bContext *C, struct wmOperator *op, const struct wmEvent *event); +int WM_gesture_box_modal_3d(struct bContext *C, + struct wmOperator *op, + const struct wmEvent *event); void WM_gesture_box_cancel(struct bContext *C, struct wmOperator *op); int WM_gesture_circle_invoke(struct bContext *C, struct wmOperator *op, @@ -932,6 +934,22 @@ bool WM_event_is_tablet(const struct wmEvent *event); int WM_event_absolute_delta_x(const struct wmEvent *event); int WM_event_absolute_delta_y(const struct wmEvent *event); +void WM_event_xr_data(const struct wmEvent *event, + char **action_set, + char **action, + char *type, + float state[2], + float state_other[2], + float *float_threshold, + float controller_loc[3], + float controller_rot[4], + float controller_loc_other[3], + float controller_rot_other[4], + float *eye_lens, + float eye_viewmat[4][4], + bool *bimanual); +bool WM_event_is_xr(const struct wmEvent *event); + #ifdef WITH_INPUT_IME bool WM_event_is_ime_switch(const struct wmEvent *event); #endif @@ -982,8 +1000,11 @@ void WM_xr_session_base_pose_reset(wmXrData *xr); bool WM_xr_session_state_viewer_pose_location_get(const wmXrData *xr, float r_location[3]); bool WM_xr_session_state_viewer_pose_rotation_get(const wmXrData *xr, float r_rotation[4]); bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr, + bool from_selection_eye, float r_viewmat[4][4], - float *r_focal_len); + float *r_focal_len, + float *r_clip_start, + float *r_clip_end); bool WM_xr_session_state_controller_grip_location_get(const wmXrData *xr, unsigned int subaction_idx, float r_location[3]); @@ -996,6 +1017,15 @@ bool WM_xr_session_state_controller_aim_location_get(const wmXrData *xr, bool WM_xr_session_state_controller_aim_rotation_get(const wmXrData *xr, unsigned int subaction_idx, float r_rotation[4]); +bool WM_xr_session_state_nav_location_get(const wmXrData *xr, float r_location[3]); +void WM_xr_session_state_nav_location_set(wmXrData *xr, const float location[3]); +bool WM_xr_session_state_nav_rotation_get(const wmXrData *xr, float r_rotation[4]); +void WM_xr_session_state_nav_rotation_set(wmXrData *xr, const float rotation[4]); +bool WM_xr_session_state_nav_scale_get(const wmXrData *xr, float *r_scale); +void WM_xr_session_state_nav_scale_set(wmXrData *xr, float scale); +void WM_xr_session_state_navigation_reset(struct wmXrSessionState *state); + +struct ARegionType *WM_xr_surface_controller_region_type_get(void); /* wm_xr_actions.c */ /* XR action functions to be called pre-XR session start. @@ -1060,20 +1090,24 @@ void WM_xr_haptic_action_stop(wmXrData *xr, const char *subaction_path); /* wm_xr_actionmap.c */ -XrActionMap *WM_xr_actionmap_new(struct wmXrRuntimeData *runtime, +void WM_xr_actionconfig_init(struct bContext *C); +void WM_xr_actionconfig_update_tag(XrActionMap *actionmap, XrActionMapItem *ami); +void WM_xr_actionconfig_update(XrSessionSettings *settings); + +XrActionConfig *WM_xr_actionconfig_new(XrSessionSettings *settings, + const char *name, + bool user_defined); +bool WM_xr_actionconfig_remove(XrSessionSettings *settings, XrActionConfig *actionconf); +XrActionConfig *WM_xr_actionconfig_active_get(XrSessionSettings *settings); +void WM_xr_actionconfig_active_set(XrSessionSettings *settings, const char *name); + +XrActionMap *WM_xr_actionmap_new(XrActionConfig *actionconf, 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); +void WM_xr_actionmap_ensure_unique(XrActionConfig *actionconf, XrActionMap *actionmap); +XrActionMap *WM_xr_actionmap_add_copy(XrActionConfig *actionconf, XrActionMap *am_src); +bool WM_xr_actionmap_remove(XrActionConfig *actionconf, XrActionMap *actionmap); +XrActionMap *WM_xr_actionmap_find(XrActionConfig *actionconf, const char *name); XrActionMapItem *WM_xr_actionmap_item_new(XrActionMap *actionmap, const char *name, @@ -1094,6 +1128,25 @@ bool WM_xr_actionmap_binding_remove(XrActionMapItem *ami, XrActionMapBinding *am XrActionMapBinding *WM_xr_actionmap_binding_find(XrActionMapItem *ami, const char *name); #endif /* WITH_XR_OPENXR */ +/* wm.c */ +/* These need to be accessible even when WITH_XR_OPENXR is not defined since actionmaps can be + * stored in files. */ +void WM_xr_actionconfig_free(XrActionConfig *actionconf); +void WM_xr_actionconfig_clear(XrActionConfig *actionconf); +void WM_xr_actionmap_clear(XrActionMap *actionmap); +void WM_xr_actionmap_item_properties_free(XrActionMapItem *ami); +void WM_xr_actionmap_item_bindings_clear(XrActionMapItem *ami); + +/* wm_xr_mocap.c */ +XrMotionCaptureObject *WM_xr_mocap_object_new(XrSessionSettings *settings, Object *ob); +void WM_xr_mocap_object_ensure_unique(XrSessionSettings *settings, + XrMotionCaptureObject *mocap_ob); +void WM_xr_mocap_object_remove(XrSessionSettings *settings, XrMotionCaptureObject *mocap_ob); +XrMotionCaptureObject *WM_xr_mocap_object_find(const XrSessionSettings *settings, + const Object *ob); + +bool WM_xr_session_state_mocap_pose_get(const wmXrData *xr, XrMotionCaptureObject *mocap_ob); +void WM_xr_session_state_mocap_pose_set(wmXrData *xr, const XrMotionCaptureObject *mocap_ob); #ifdef __cplusplus } #endif diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 588c895c742..2f98834703c 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -109,17 +109,20 @@ #pragma once struct ID; +struct IDProperty; struct ImBuf; struct bContext; struct wmDrag; struct wmDropBox; struct wmEvent; struct wmOperator; +struct wmOperatorType; struct wmWindowManager; #include "BLI_compiler_attrs.h" #include "DNA_listBase.h" #include "DNA_vec_types.h" +#include "DNA_xr_types.h" #include "RNA_types.h" /* exported types for WM */ @@ -723,6 +726,41 @@ typedef struct wmXrActionState { } wmXrActionState; #endif +typedef struct wmXrActionData { + /** Action set name. */ + char action_set[64]; + /** Action name. */ + char action[64]; + /** Type. */ + eXrActionType type; + /** State. Set appropriately based on type. */ + float state[2]; + /** State of the other subaction path for bimanual actions. */ + float state_other[2]; + + /** Input threshold for float/vector2f actions. */ + float float_threshold; + + /** Controller aim pose corresponding to the action's subaction path. */ + float controller_loc[3]; + float controller_rot[4]; + /** Controller aim pose of the other subaction path for bimanual actions. */ + float controller_loc_other[3]; + float controller_rot_other[4]; + + /** Lens (focal length) of the selection eye. */ + float eye_lens; + /** Viewmat of the selection eye. */ + float eye_viewmat[4][4]; + + /** Operator. */ + struct wmOperatorType *ot; + struct IDProperty *op_properties; + + /** Whether bimanual interaction is occuring. */ + bool bimanual; +} wmXrActionData; + /** Timer flags. */ typedef enum { /** Do not attempt to free customdata pointer even if non-NULL. */ @@ -797,6 +835,14 @@ typedef struct wmOperatorType { const struct wmEvent *) ATTR_WARN_UNUSED_RESULT; /** + * Same as invoke() but intended to be called from an XR session. + * The event type should be EVT_XR_ACTION and custom data type EVT_DATA_XR. + */ + int (*invoke_3d)(struct bContext *, + struct wmOperator *, + const struct wmEvent *) ATTR_WARN_UNUSED_RESULT; + + /** * Called when a modal operator is canceled (not used often). * Internal cleanup can be done here if needed. */ @@ -813,6 +859,14 @@ typedef struct wmOperatorType { const struct wmEvent *) ATTR_WARN_UNUSED_RESULT; /** + * Same as modal() but intended to be called from an XR session. + * The event type should be EVT_XR_ACTION and custom data type EVT_DATA_XR. + */ + int (*modal_3d)(struct bContext *, + struct wmOperator *, + const struct wmEvent *) ATTR_WARN_UNUSED_RESULT; + + /** * Verify if the operator can be executed in the current context, note * that the operator might still fail to execute even if this return true. */ diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c index 0b7d5e5f1f4..27fe0d1c7b5 100644 --- a/source/blender/windowmanager/intern/wm.c +++ b/source/blender/windowmanager/intern/wm.c @@ -106,6 +106,32 @@ static void window_manager_foreach_id(ID *id, LibraryForeachIDData *data) static void write_wm_xr_data(BlendWriter *writer, wmXrData *xr_data) { BKE_screen_view3d_shading_blend_write(writer, &xr_data->session_settings.shading); + + LISTBASE_FOREACH (XrActionConfig *, ac, &xr_data->session_settings.actionconfigs) { + BLO_write_struct(writer, XrActionConfig, ac); + + /* Only write actionmaps for user configs. */ + if ((ac->flag & XR_ACTIONCONF_USER) != 0) { + LISTBASE_FOREACH (XrActionMap *, am, &ac->actionmaps) { + BLO_write_struct(writer, XrActionMap, am); + + LISTBASE_FOREACH (XrActionMapItem *, ami, &am->items) { + BLO_write_struct(writer, XrActionMapItem, ami); + if (ami->op[0] && ami->op_properties) { + IDP_BlendWrite(writer, ami->op_properties); + } + + LISTBASE_FOREACH (XrActionMapBinding *, amb, &ami->bindings) { + BLO_write_struct(writer, XrActionMapBinding, amb); + } + } + } + } + } + + LISTBASE_FOREACH (XrMotionCaptureObject *, mocap_ob, &xr_data->session_settings.mocap_objects) { + BLO_write_struct(writer, XrMotionCaptureObject, mocap_ob); + } } static void window_manager_blend_write(BlendWriter *writer, ID *id, const void *id_address) @@ -134,6 +160,39 @@ static void window_manager_blend_write(BlendWriter *writer, ID *id, const void * static void direct_link_wm_xr_data(BlendDataReader *reader, wmXrData *xr_data) { BKE_screen_view3d_shading_blend_read_data(reader, &xr_data->session_settings.shading); + + BLO_read_list(reader, &xr_data->session_settings.actionconfigs); + + LISTBASE_FOREACH (XrActionConfig *, ac, &xr_data->session_settings.actionconfigs) { + BLO_read_list(reader, &ac->actionmaps); + + LISTBASE_FOREACH (XrActionMap *, am, &ac->actionmaps) { + BLO_read_list(reader, &am->items); + + LISTBASE_FOREACH (XrActionMapItem *, ami, &am->items) { + if (ami->op[0] && ami->op_properties) { + BLO_read_data_address(reader, &ami->op_properties); + IDP_BlendDataRead(reader, &ami->op_properties); + + ami->op_properties_ptr = MEM_callocN(sizeof(PointerRNA), "wmOpItemPtr"); + WM_operator_properties_create(ami->op_properties_ptr, ami->op); + ami->op_properties_ptr->data = ami->op_properties; + } + else { + ami->op_properties = NULL; + ami->op_properties_ptr = NULL; + } + + BLO_read_list(reader, &ami->bindings); + } + } + } + + BLO_read_data_address(reader, &xr_data->session_settings.defaultconf); + BLO_read_data_address(reader, &xr_data->session_settings.addonconf); + BLO_read_data_address(reader, &xr_data->session_settings.userconf); + + BLO_read_list(reader, &xr_data->session_settings.mocap_objects); } static void window_manager_blend_read_data(BlendDataReader *reader, ID *id) @@ -227,6 +286,10 @@ static void window_manager_blend_read_data(BlendDataReader *reader, ID *id) static void lib_link_wm_xr_data(BlendLibReader *reader, ID *parent_id, wmXrData *xr_data) { BLO_read_id_address(reader, parent_id->lib, &xr_data->session_settings.base_pose_object); + + LISTBASE_FOREACH (XrMotionCaptureObject *, mocap_ob, &xr_data->session_settings.mocap_objects) { + BLO_read_id_address(reader, parent_id->lib, &mocap_ob->ob); + } } static void lib_link_workspace_instance_hook(BlendLibReader *reader, @@ -433,6 +496,55 @@ void WM_operator_handlers_clear(wmWindowManager *wm, wmOperatorType *ot) } } +void WM_xr_actionmap_item_bindings_clear(XrActionMapItem *ami) +{ + BLI_freelistN(&ami->bindings); + + ami->selbinding = 0; +} + +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); + } +} + +void WM_xr_actionmap_clear(XrActionMap *actionmap) +{ + LISTBASE_FOREACH (XrActionMapItem *, ami, &actionmap->items) { + WM_xr_actionmap_item_bindings_clear(ami); + WM_xr_actionmap_item_properties_free(ami); + } + + BLI_freelistN(&actionmap->items); + + actionmap->selitem = 0; +} + +void WM_xr_actionconfig_clear(XrActionConfig *actionconf) +{ + LISTBASE_FOREACH (XrActionMap *, am, &actionconf->actionmaps) { + WM_xr_actionmap_clear(am); + } + + BLI_freelistN(&actionconf->actionmaps); + + actionconf->selactionmap = actionconf->actactionmap = 0; +} + +void WM_xr_actionconfig_free(XrActionConfig *actionconf) +{ + WM_xr_actionconfig_clear(actionconf); + MEM_freeN(actionconf); +} + /* ****************************************** */ void WM_keyconfig_reload(bContext *C) @@ -507,6 +619,9 @@ void WM_check(bContext *C) /* Case: fileread. */ if ((wm->initialized & WM_WINDOW_IS_INIT) == 0) { WM_keyconfig_init(C); +#ifdef WITH_XR_OPENXR + WM_xr_actionconfig_init(C); +#endif WM_autosave_init(wm); } @@ -595,6 +710,13 @@ void wm_close_and_free(bContext *C, wmWindowManager *wm) WM_keyconfig_free(keyconf); } + XrActionConfig *actionconf; + while ((actionconf = BLI_pophead(&wm->xr.session_settings.actionconfigs))) { + WM_xr_actionconfig_free(actionconf); + } + + BLI_freelistN(&wm->xr.session_settings.mocap_objects); + BLI_freelistN(&wm->notifier_queue); if (wm->message_bus != NULL) { diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c index 7b5691b99a0..364aab9482a 100644 --- a/source/blender/windowmanager/intern/wm_event_query.c +++ b/source/blender/windowmanager/intern/wm_event_query.c @@ -486,6 +486,75 @@ int WM_event_absolute_delta_y(const struct wmEvent *event) /** \} */ /* -------------------------------------------------------------------- */ +/** \name XR Input Access + * \{ */ + +void WM_event_xr_data(const wmEvent *event, + char **action_set, + char **action, + char *type, + float state[2], + float state_other[2], + float *float_threshold, + float controller_loc[3], + float controller_rot[4], + float controller_loc_other[3], + float controller_rot_other[4], + float *eye_lens, + float eye_viewmat[4][4], + bool *bimanual) +{ + const wmXrActionData *data = event->customdata; + + if (action_set) { + strcpy(*action_set, data->action_set); + } + if (action) { + strcpy(*action, data->action); + } + if (type) { + *type = data->type; + } + if (state) { + copy_v2_v2(state, data->state); + } + if (state_other) { + copy_v2_v2(state_other, data->state_other); + } + if (float_threshold) { + *float_threshold = data->float_threshold; + } + if (controller_loc) { + copy_v3_v3(controller_loc, data->controller_loc); + } + if (controller_rot) { + copy_v4_v4(controller_rot, data->controller_rot); + } + if (controller_loc_other) { + copy_v3_v3(controller_loc_other, data->controller_loc_other); + } + if (controller_rot_other) { + copy_v4_v4(controller_rot_other, data->controller_rot_other); + } + if (eye_lens) { + *eye_lens = data->eye_lens; + } + if (eye_viewmat) { + copy_m4_m4(eye_viewmat, data->eye_viewmat); + } + if (bimanual) { + *bimanual = data->bimanual; + } +} + +bool WM_event_is_xr(const struct wmEvent *event) +{ + return (event->custom == EVT_DATA_XR); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Event IME Input Access * \{ */ diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 537d5264ba9..f025fd43b49 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -83,6 +83,7 @@ #include "wm.h" #include "wm_event_system.h" #include "wm_event_types.h" +#include "wm_surface.h" #include "wm_window.h" #include "DEG_depsgraph.h" @@ -1341,7 +1342,19 @@ static int wm_operator_invoke(bContext *C, ot->idname); } - if (op->type->invoke && event) { + if (op->type->invoke_3d && event && (event->type == EVT_XR_ACTION)) { + if (op->type->flag & OPTYPE_UNDO) { + wm->op_undo_depth++; + } + + retval = op->type->invoke_3d(C, op, event); + OPERATOR_RETVAL_CHECK(retval); + + if (op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) { + wm->op_undo_depth--; + } + } + else if (op->type->invoke && event) { wm_region_mouse_co(C, event); if (op->type->flag & OPTYPE_UNDO) { @@ -2227,7 +2240,7 @@ static int wm_handler_operator_call(bContext *C, * nothing to do in this case. */ } - else if (ot->modal) { + else if (ot->modal || ot->modal_3d) { /* We set context to where modal handler came from. */ wmWindowManager *wm = CTX_wm_manager(C); wmWindow *win = CTX_wm_window(C); @@ -2245,7 +2258,15 @@ static int wm_handler_operator_call(bContext *C, } /* Warning, after this call all context data and 'event' may be freed. see check below. */ - retval = ot->modal(C, op, event); + if (ot->modal_3d && event->type == EVT_XR_ACTION) { + retval = ot->modal_3d(C, op, event); + } + else if (ot->modal) { + retval = ot->modal(C, op, event); + } + else { + /* Pass through. An "XR operator" (only modal_3d) received a non-XR event.*/ + } OPERATOR_RETVAL_CHECK(retval); if (ot->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) { @@ -3434,6 +3455,71 @@ static void wm_event_free_and_remove_from_queue_if_valid(wmEvent *event) * Handle events for all windows, run from the #WM_main event loop. * \{ */ +#ifdef WITH_XR_OPENXR +static void wm_event_handle_xrevent( + bContext *C, wmWindowManager *wm, wmWindow *win, bScreen *screen, wmEvent *event) +{ + int action = 0; + + /* Find a valid region for XR operator execution and modal handling. */ + ED_screen_areas_iter (win, screen, area) { + CTX_wm_area_set(C, area); + if (!CTX_wm_view3d(C)) { + continue; + } + + LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { + if (!WM_region_use_viewport(area, region)) { + continue; + } + CTX_wm_region_set(C, region); + + action |= wm_handlers_do(C, event, &win->modalhandlers); + + if ((action & WM_HANDLER_BREAK) == 0) { + wmXrActionData *actiondata = event->customdata; + if ((actiondata->ot->modal || actiondata->ot->modal_3d) && event->val == KM_RELEASE) { + /* Don't execute modal operators on release. */ + } + else { + PointerRNA properties = {.type = actiondata->ot->srna, + .data = actiondata->op_properties}; + if (actiondata->ot->invoke || actiondata->ot->invoke_3d) { + /* Invoke operator, either executing operator or transferring responsibility to window + * modal handlers. */ + wm_operator_invoke(C, + actiondata->ot, + event, + actiondata->op_properties ? &properties : NULL, + NULL, + false, + false); + } + else { + /* Execute operator. */ + wmOperator *op = wm_operator_create( + wm, actiondata->ot, actiondata->op_properties ? &properties : NULL, NULL); + if ((WM_operator_call(C, op) & OPERATOR_HANDLED) == 0) { + WM_operator_free(op); + } + } + } + action |= WM_HANDLER_BREAK; + } + + CTX_wm_region_set(C, NULL); + break; + } + + if (action & WM_HANDLER_BREAK) { + break; + } + } + + CTX_wm_area_set(C, NULL); +} +#endif /* WITH_XR_OPENXR */ + /* Called in main loop. */ /* Goes over entire hierarchy: events -> window -> screen -> area -> region. */ void wm_event_do_handlers(bContext *C) @@ -3531,6 +3617,16 @@ void wm_event_do_handlers(bContext *C) CTX_wm_window_set(C, win); +#ifdef WITH_XR_OPENXR + if (event->type == EVT_XR_ACTION) { + wm_event_handle_xrevent(C, wm, win, screen, event); + BLI_remlink(&win->event_queue, event); + wm_event_free(event); + /* Skip mouse event handling below, which is unnecessary for XR events. */ + continue; + } +#endif + /* Clear tool-tip on mouse move. */ if (screen->tool_tip && screen->tool_tip->exit_on_event) { if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { @@ -5083,6 +5179,24 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void #endif } +#ifdef WITH_XR_OPENXR +void wm_event_add_xrevent(wmWindow *win, wmXrActionData *actiondata, short val) +{ + BLI_assert(val == KM_PRESS || val == KM_RELEASE); + + wmEvent event = { + .type = EVT_XR_ACTION, + .val = val, + .is_repeat = false, + .custom = EVT_DATA_XR, + .customdata = actiondata, + .customdatafree = 1, + }; + + wm_event_add(win, &event); +} +#endif /* WITH_XR_OPENXR */ + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index a5ebf988edd..24a3b58fc26 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -204,6 +204,18 @@ static void wm_window_match_init(bContext *C, ListBase *wmlist) WM_event_remove_handlers(C, &win->modalhandlers); ED_screen_exit(C, win, WM_window_get_active_screen(win)); } + + /* Free XR actionconfigs and motion capture objects. */ + XrSessionSettings *xrsettings = &wm->xr.session_settings; + XrActionConfig *actionconf; + while ((actionconf = BLI_pophead(&xrsettings->actionconfigs))) { + WM_xr_actionconfig_free(actionconf); + } + xrsettings->defaultconf = NULL; + xrsettings->addonconf = NULL; + xrsettings->userconf = NULL; + + BLI_freelistN(&xrsettings->mocap_objects); } /* reset active window */ diff --git a/source/blender/windowmanager/intern/wm_gesture_ops.c b/source/blender/windowmanager/intern/wm_gesture_ops.c index 578699dda60..b087bd9d668 100644 --- a/source/blender/windowmanager/intern/wm_gesture_ops.c +++ b/source/blender/windowmanager/intern/wm_gesture_ops.c @@ -36,6 +36,8 @@ #include "BKE_context.h" +#include "DEG_depsgraph.h" + #include "WM_api.h" #include "WM_types.h" @@ -45,6 +47,9 @@ #include "ED_screen.h" #include "ED_select_utils.h" +#include "ED_view3d.h" + +#include "../editors/space_view3d/view3d_intern.h" #include "UI_interface.h" @@ -202,6 +207,60 @@ int WM_gesture_box_invoke(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_RUNNING_MODAL; } +int WM_gesture_box_invoke_3d(bContext *C, wmOperator *op, const wmEvent *event) +{ + BLI_assert(event->type == EVT_XR_ACTION); + BLI_assert(event->custom == EVT_DATA_XR); + BLI_assert(event->customdata); + + const wmXrActionData *actiondata = event->customdata; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + View3D *v3d = CTX_wm_view3d(C); + ARegion *region = CTX_wm_region(C); + RegionView3D *rv3d = region->regiondata; + wmWindowManager *wm = CTX_wm_manager(C); + wmXrData *xr = &wm->xr; + float lens_prev; + float clip_start_prev, clip_end_prev; + int mval[2]; + int retval; + + wmEvent event_mut; + memcpy(&event_mut, event, sizeof(wmEvent)); + + /* Replace window view parameters with XR surface counterparts. */ + ED_view3d_view_params_get(v3d, rv3d, &lens_prev, &clip_start_prev, &clip_end_prev, NULL); + ED_view3d_view_params_set(depsgraph, + scene, + v3d, + region, + actiondata->eye_lens, + xr->session_settings.clip_start, + xr->session_settings.clip_end, + NULL); + + map_to_pixel(mval, + actiondata->controller_loc, + actiondata->eye_viewmat, + rv3d->winmat, + region->winx, + region->winy); + + event_mut.x = region->winrct.xmin + mval[0]; + event_mut.y = region->winrct.ymin + mval[1]; + + RNA_boolean_set(op->ptr, "wait_for_input", false); + + retval = WM_gesture_box_invoke(C, op, &event_mut); + + /* Restore window view. */ + ED_view3d_view_params_set( + depsgraph, scene, v3d, region, lens_prev, clip_start_prev, clip_end_prev, NULL); + + return retval; +} + int WM_gesture_box_modal(bContext *C, wmOperator *op, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); @@ -280,6 +339,85 @@ int WM_gesture_box_modal(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_RUNNING_MODAL; } +int WM_gesture_box_modal_3d(bContext *C, wmOperator *op, const wmEvent *event) +{ + BLI_assert(event->type == EVT_XR_ACTION); + BLI_assert(event->custom == EVT_DATA_XR); + BLI_assert(event->customdata); + + const bool release = (event->val == KM_RELEASE); + + const wmXrActionData *actiondata = event->customdata; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + View3D *v3d = CTX_wm_view3d(C); + ARegion *region = CTX_wm_region(C); + RegionView3D *rv3d = region->regiondata; + wmWindowManager *wm = CTX_wm_manager(C); + wmXrData *xr = &wm->xr; + float lens_prev; + float clip_start_prev, clip_end_prev; + float viewmat_prev[4][4]; + int mval[2]; + int retval; + + wmEvent event_mut; + memcpy(&event_mut, event, sizeof(wmEvent)); + + /* Since this function is called in a window context, we need to replace the + * window view parameters with the XR surface counterparts to get a correct + * result for some operators (e.g. GPU select). */ + ED_view3d_view_params_get( + v3d, rv3d, &lens_prev, &clip_start_prev, &clip_end_prev, release ? viewmat_prev : NULL); + ED_view3d_view_params_set(depsgraph, + scene, + v3d, + region, + actiondata->eye_lens, + xr->session_settings.clip_start, + xr->session_settings.clip_end, + release ? actiondata->eye_viewmat : NULL); + + map_to_pixel(mval, + actiondata->controller_loc, + actiondata->eye_viewmat, + rv3d->winmat, + region->winx, + region->winy); + + if (event->val == KM_PRESS) { + event_mut.type = MOUSEMOVE; + { + wmGesture *gesture = op->customdata; + gesture->is_active = true; + } + event_mut.x = region->winrct.xmin + mval[0]; + event_mut.y = region->winrct.ymin + mval[1]; + } + else if (event->val == KM_RELEASE) { + event_mut.type = EVT_MODAL_MAP; + event_mut.val = GESTURE_MODAL_SELECT; + } + else { + /* XR events currently only support press and release. */ + BLI_assert(false); + } + + retval = WM_gesture_box_modal(C, op, &event_mut); + + /* Restore window view. */ + ED_view3d_view_params_set(depsgraph, + scene, + v3d, + region, + lens_prev, + clip_start_prev, + clip_end_prev, + release ? viewmat_prev : NULL); + + return retval; +} + void WM_gesture_box_cancel(bContext *C, wmOperator *op) { gesture_modal_end(C, op); diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index 81dcc5ccea0..79368fa9eef 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -3760,87 +3760,6 @@ static void WM_OT_stereo3d_set(wmOperatorType *ot) /** \} */ -#ifdef WITH_XR_OPENXR - -static void wm_xr_session_update_screen(Main *bmain, const wmXrData *xr_data) -{ - const bool session_exists = WM_xr_session_exists(xr_data); - - for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) { - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - LISTBASE_FOREACH (SpaceLink *, slink, &area->spacedata) { - if (slink->spacetype == SPACE_VIEW3D) { - View3D *v3d = (View3D *)slink; - - if (v3d->flag & V3D_XR_SESSION_MIRROR) { - ED_view3d_xr_mirror_update(area, v3d, session_exists); - } - - if (session_exists) { - wmWindowManager *wm = bmain->wm.first; - const Scene *scene = WM_windows_scene_get_from_screen(wm, screen); - - ED_view3d_xr_shading_update(wm, v3d, scene); - } - /* Ensure no 3D View is tagged as session root. */ - else { - v3d->runtime.flag &= ~V3D_RUNTIME_XR_SESSION_ROOT; - } - } - } - } - } - - WM_main_add_notifier(NC_WM | ND_XR_DATA_CHANGED, NULL); -} - -static void wm_xr_session_update_screen_on_exit_cb(const wmXrData *xr_data) -{ - /* Just use G_MAIN here, storing main isn't reliable enough on file read or exit. */ - wm_xr_session_update_screen(G_MAIN, xr_data); -} - -static int wm_xr_session_toggle_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Main *bmain = CTX_data_main(C); - wmWindowManager *wm = CTX_wm_manager(C); - wmWindow *win = CTX_wm_window(C); - View3D *v3d = CTX_wm_view3d(C); - - /* Lazy-create xr context - tries to dynlink to the runtime, reading active_runtime.json. */ - if (wm_xr_init(wm) == false) { - return OPERATOR_CANCELLED; - } - - v3d->runtime.flag |= V3D_RUNTIME_XR_SESSION_ROOT; - wm_xr_session_toggle(wm, win, wm_xr_session_update_screen_on_exit_cb); - wm_xr_session_update_screen(bmain, &wm->xr); - - WM_event_add_notifier(C, NC_WM | ND_XR_DATA_CHANGED, NULL); - - return OPERATOR_FINISHED; -} - -static void WM_OT_xr_session_toggle(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Toggle VR Session"; - ot->idname = "WM_OT_xr_session_toggle"; - ot->description = - "Open a view for use with virtual reality headsets, or close it if already " - "opened"; - - /* callbacks */ - ot->exec = wm_xr_session_toggle_exec; - ot->poll = ED_operator_view3d_active; - - /* XXX INTERNAL just to hide it from the search menu by default, an Add-on will expose it in the - * UI instead. Not meant as a permanent solution. */ - ot->flag = OPTYPE_INTERNAL; -} - -#endif /* WITH_XR_OPENXR */ - /* -------------------------------------------------------------------- */ /** \name Operator Registration & Keymaps * \{ */ @@ -3882,9 +3801,6 @@ void wm_operatortypes_register(void) WM_operatortype_append(WM_OT_call_panel); WM_operatortype_append(WM_OT_radial_control); WM_operatortype_append(WM_OT_stereo3d_set); -#ifdef WITH_XR_OPENXR - WM_operatortype_append(WM_OT_xr_session_toggle); -#endif #if defined(WIN32) WM_operatortype_append(WM_OT_console_toggle); #endif @@ -3895,6 +3811,10 @@ void wm_operatortypes_register(void) /* gizmos */ WM_operatortype_append(GIZMOGROUP_OT_gizmo_select); WM_operatortype_append(GIZMOGROUP_OT_gizmo_tweak); + +#ifdef WITH_XR_OPENXR + wm_xr_operatortypes_register(); +#endif } /* circleselect-like modal operators */ diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 8baf4a0e013..55b0d267cec 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -1561,7 +1561,7 @@ void wm_window_process_events(const bContext *C) #ifdef WITH_XR_OPENXR /* XR events don't use the regular window queues. So here we don't only trigger * processing/dispatching but also handling. */ - has_event |= wm_xr_events_handle(CTX_wm_manager(C)); + has_event |= wm_xr_events_handle(C); #endif /* When there is no event, sleep 5 milliseconds not to use too much CPU when idle. diff --git a/source/blender/windowmanager/wm_event_system.h b/source/blender/windowmanager/wm_event_system.h index 787c840de8a..f6d24fbe9b1 100644 --- a/source/blender/windowmanager/wm_event_system.h +++ b/source/blender/windowmanager/wm_event_system.h @@ -33,6 +33,10 @@ struct ARegion; struct GHOST_TabletData; struct ScrArea; +#ifdef WITH_XR_OPENXR +struct wmXrActionData; +#endif + #ifdef __cplusplus extern "C" { #endif @@ -147,6 +151,9 @@ void wm_event_free_handler(wmEventHandler *handler); 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, wmXrActionData *actiondata, short val); +#endif void wm_event_do_depsgraph(bContext *C, bool is_after_open_file); void wm_event_do_refresh_wm_and_depsgraph(bContext *C); diff --git a/source/blender/windowmanager/wm_event_types.h b/source/blender/windowmanager/wm_event_types.h index 905c57d901a..fabd77847c4 100644 --- a/source/blender/windowmanager/wm_event_types.h +++ b/source/blender/windowmanager/wm_event_types.h @@ -34,6 +34,7 @@ enum { EVT_DATA_TIMER = 2, EVT_DATA_DRAGDROP = 3, EVT_DATA_NDOF_MOTION = 4, + EVT_DATA_XR = 5, }; /* tablet active, matches GHOST_TTabletMode */ @@ -341,6 +342,10 @@ enum { /* could become gizmo callback */ EVT_GIZMO_UPDATE = 0x5025, /* 20517 */ + + /* XR events: 0x503x */ + EVT_XR_ACTION = 0x5030, /* 20528 */ + /* ********** End of Blender internal events. ********** */ }; diff --git a/source/blender/windowmanager/xr/intern/wm_xr.c b/source/blender/windowmanager/xr/intern/wm_xr.c index 8891840cb75..2b6a2abd64b 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr.c +++ b/source/blender/windowmanager/xr/intern/wm_xr.c @@ -22,6 +22,7 @@ * representation of the OpenXR runtime connection within the application. */ +#include "BKE_context.h" #include "BKE_global.h" #include "BKE_idprop.h" #include "BKE_report.h" @@ -54,6 +55,7 @@ static void wm_xr_error_handler(const GHOST_XrError *error) wmWindowManager *wm = handler_data->wm; BKE_reports_clear(&wm->reports); + WM_report(RPT_ERROR, error->user_message); WM_report_banner_show(); @@ -130,14 +132,16 @@ void wm_xr_exit(wmWindowManager *wm) } } -bool wm_xr_events_handle(wmWindowManager *wm) +bool wm_xr_events_handle(const bContext *C) { + wmWindowManager *wm = CTX_wm_manager(C); + if (wm->xr.runtime && wm->xr.runtime->context) { GHOST_XrEventsHandle(wm->xr.runtime->context); - /* Process OpenXR action events. */ + /* Process OpenXR action events and dispatch to XR surface / window queues. */ if (WM_xr_session_is_ready(&wm->xr)) { - wm_xr_session_actions_update(&wm->xr); + wm_xr_session_actions_update(C); } /* wm_window_process_events() uses the return value to determine if it can put the main thread @@ -173,7 +177,6 @@ void wm_xr_runtime_data_free(wmXrRuntimeData **runtime) (*runtime)->context = NULL; wm_xr_session_data_free(&(*runtime)->session_state); - WM_xr_actionmaps_clear(*runtime); GHOST_XrContextDestroy(context); } diff --git a/source/blender/windowmanager/xr/intern/wm_xr_actionmap.c b/source/blender/windowmanager/xr/intern/wm_xr_actionmap.c index 8903305adb4..42149d5552d 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_actionmap.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_actionmap.c @@ -26,24 +26,67 @@ #include <string.h> #include "BKE_context.h" +#include "BKE_global.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_ACTIONCONF_STR_DEFAULT "blender" #define WM_XR_ACTIONMAP_STR_DEFAULT "actionmap" #define WM_XR_ACTIONMAP_ITEM_STR_DEFAULT "action" #define WM_XR_ACTIONMAP_BINDING_STR_DEFAULT "binding" +/* Replacement for U.keyconfigstr for actionconfigs. */ +static char g_xr_actionconfigstr[64]; + +/* Actionconfig update flag. */ +enum { + WM_XR_ACTIONCONF_UPDATE_ACTIVE = (1 << 0), /* Update active actionconfig. */ + WM_XR_ACTIONCONF_UPDATE_ALL = (1 << 1), /* Update all actionconfigs. */ + WM_XR_ACTIONCONF_UPDATE_ENSURE = + (1 << 2), /* Skip actionmap/item flag checks when updating actionconfigs. */ +}; + +static char g_xr_actionconfig_update_flag = 0; + +/* -------------------------------------------------------------------- */ + +void WM_xr_actionconfig_update_tag(XrActionMap *actionmap, XrActionMapItem *ami) +{ + g_xr_actionconfig_update_flag |= WM_XR_ACTIONCONF_UPDATE_ACTIVE; + + if (actionmap) { + actionmap->flag |= XR_ACTIONMAP_UPDATE; + } + if (ami) { + ami->flag |= XR_ACTIONMAP_ITEM_UPDATE; + } +} + +#if 0 /* Currently unused. */ +static void wm_xr_actionconfig_update_active(XrActionMap *actionmap, XrActionMapItem *ami, bool ensure) +{ + WM_xr_actionconfig_update_tag(actionmap, ami); + if (ensure) { + g_xr_actionconfig_update_flag |= WM_XR_ACTIONCONF_UPDATE_ENSURE; + } +} + +static void wm_xr_actionconfig_update_all(bool ensure) +{ + g_xr_actionconfig_update_flag |= WM_XR_ACTIONCONF_UPDATE_ALL; + if (ensure) { + g_xr_actionconfig_update_flag |= WM_XR_ACTIONCONF_UPDATE_ENSURE; + } +} +#endif + /* -------------------------------------------------------------------- */ /** \name Action Map Binding * @@ -51,16 +94,16 @@ * \{ */ XrActionMapBinding *WM_xr_actionmap_binding_new(XrActionMapItem *ami, - const char *name, + const char *idname, bool replace_existing) { - XrActionMapBinding *amb_prev = WM_xr_actionmap_binding_find(ami, name); + XrActionMapBinding *amb_prev = WM_xr_actionmap_binding_find(ami, idname); if (amb_prev && replace_existing) { return amb_prev; } XrActionMapBinding *amb = MEM_callocN(sizeof(XrActionMapBinding), __func__); - BLI_strncpy(amb->name, name, MAX_NAME); + BLI_strncpy(amb->name, idname, MAX_NAME); if (amb_prev) { WM_xr_actionmap_binding_ensure_unique(ami, amb); } @@ -173,31 +216,12 @@ XrActionMapBinding *WM_xr_actionmap_binding_find(XrActionMapItem *ami, const cha * Item in an XR action map, that maps an XR event to an operator, pose, or haptic output. * \{ */ -static void wm_xr_actionmap_item_bindings_clear(XrActionMapItem *ami) -{ - BLI_freelistN(&ami->bindings); - ami->selbinding = 0; -} - 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. @@ -211,13 +235,13 @@ void WM_xr_actionmap_item_properties_update_ot(XrActionMapItem *ami) break; case XR_POSE_INPUT: case XR_VIBRATION_OUTPUT: - wm_xr_actionmap_item_properties_free(ami); + 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); + WM_xr_actionmap_item_properties_free(ami); return; } @@ -237,7 +261,33 @@ void WM_xr_actionmap_item_properties_update_ot(XrActionMapItem *ami) } } else { - wm_xr_actionmap_item_properties_free(ami); + WM_xr_actionmap_item_properties_free(ami); + } + } +} + +static void wm_xr_actionmap_item_properties_update_ot_from_list(ListBase *am_lb, bool ensure) +{ + if (ensure) { + LISTBASE_FOREACH (XrActionMap *, am, am_lb) { + LISTBASE_FOREACH (XrActionMapItem *, ami, &am->items) { + WM_xr_actionmap_item_properties_update_ot(ami); + ami->flag &= ~XR_ACTIONMAP_ITEM_UPDATE; + } + am->flag &= ~XR_ACTIONMAP_UPDATE; + } + } + else { + LISTBASE_FOREACH (XrActionMap *, am, am_lb) { + if ((am->flag & XR_ACTIONMAP_UPDATE) != 0) { + LISTBASE_FOREACH (XrActionMapItem *, ami, &am->items) { + if ((ami->flag & XR_ACTIONMAP_ITEM_UPDATE) != 0) { + WM_xr_actionmap_item_properties_update_ot(ami); + ami->flag &= ~XR_ACTIONMAP_ITEM_UPDATE; + } + } + am->flag &= ~XR_ACTIONMAP_UPDATE; + } } } } @@ -248,7 +298,7 @@ XrActionMapItem *WM_xr_actionmap_item_new(XrActionMap *actionmap, { XrActionMapItem *ami_prev = WM_xr_actionmap_item_find(actionmap, name); if (ami_prev && replace_existing) { - wm_xr_actionmap_item_properties_free(ami_prev); + WM_xr_actionmap_item_properties_free(ami_prev); return ami_prev; } @@ -263,12 +313,14 @@ XrActionMapItem *WM_xr_actionmap_item_new(XrActionMap *actionmap, /* Set type to float (button) input by default. */ ami->type = XR_FLOAT_INPUT; + WM_xr_actionconfig_update_tag(actionmap, ami); + return ami; } static XrActionMapItem *wm_xr_actionmap_item_find_except(XrActionMap *actionmap, const char *name, - const XrActionMapItem *amiexcept) + XrActionMapItem *amiexcept) { LISTBASE_FOREACH (XrActionMapItem *, ami, &actionmap->items) { if (STREQLEN(name, ami->name, MAX_NAME) && (ami != amiexcept)) { @@ -308,25 +360,32 @@ void WM_xr_actionmap_item_ensure_unique(XrActionMap *actionmap, XrActionMapItem BLI_strncpy(ami->name, name, MAX_NAME); } -static XrActionMapItem *wm_xr_actionmap_item_copy(XrActionMapItem *ami) +static XrActionMapItem *wm_xr_actionmap_item_copy(XrActionMapItem *ami_src) { - XrActionMapItem *amin = MEM_dupallocN(ami); + XrActionMapItem *ami_dst = MEM_dupallocN(ami_src); + + ami_dst->prev = ami_dst->next = NULL; + BLI_listbase_clear(&ami_dst->bindings); + ami_dst->flag &= ~XR_ACTIONMAP_ITEM_UPDATE; - amin->prev = amin->next = NULL; + LISTBASE_FOREACH (XrActionMapBinding *, amb, &ami_src->bindings) { + XrActionMapBinding *amb_new = wm_xr_actionmap_binding_copy(amb); + BLI_addtail(&ami_dst->bindings, amb_new); + } - if (amin->op_properties) { - amin->op_properties_ptr = MEM_callocN(sizeof(PointerRNA), "wmOpItemPtr"); - WM_operator_properties_create(amin->op_properties_ptr, amin->op); + if (ami_dst->op_properties) { + ami_dst->op_properties_ptr = MEM_callocN(sizeof(PointerRNA), "wmOpItemPtr"); + WM_operator_properties_create(ami_dst->op_properties_ptr, ami_dst->op); - amin->op_properties = IDP_CopyProperty(amin->op_properties); - amin->op_properties_ptr->data = amin->op_properties; + ami_dst->op_properties = IDP_CopyProperty(ami_dst->op_properties); + ami_dst->op_properties_ptr->data = ami_dst->op_properties; } else { - amin->op_properties = NULL; - amin->op_properties_ptr = NULL; + ami_dst->op_properties = NULL; + ami_dst->op_properties_ptr = NULL; } - return amin; + return ami_dst; } XrActionMapItem *WM_xr_actionmap_item_add_copy(XrActionMap *actionmap, XrActionMapItem *ami_src) @@ -337,6 +396,8 @@ XrActionMapItem *WM_xr_actionmap_item_add_copy(XrActionMap *actionmap, XrActionM BLI_addtail(&actionmap->items, ami_dst); + WM_xr_actionconfig_update_tag(actionmap, ami_dst); + return ami_dst; } @@ -345,8 +406,8 @@ bool WM_xr_actionmap_item_remove(XrActionMap *actionmap, XrActionMapItem *ami) int idx = BLI_findindex(&actionmap->items, ami); if (idx != -1) { - wm_xr_actionmap_item_bindings_clear(ami); - wm_xr_actionmap_item_properties_free(ami); + WM_xr_actionmap_item_bindings_clear(ami); + WM_xr_actionmap_item_properties_free(ami); BLI_freelinkN(&actionmap->items, ami); if (idx <= actionmap->selitem) { @@ -355,6 +416,8 @@ bool WM_xr_actionmap_item_remove(XrActionMap *actionmap, XrActionMapItem *ami) } } + WM_xr_actionconfig_update_tag(actionmap, NULL); + return true; } @@ -379,9 +442,11 @@ XrActionMapItem *WM_xr_actionmap_item_find(XrActionMap *actionmap, const char *n * List of XR action map items. * \{ */ -XrActionMap *WM_xr_actionmap_new(wmXrRuntimeData *runtime, const char *name, bool replace_existing) +XrActionMap *WM_xr_actionmap_new(XrActionConfig *actionconf, + const char *name, + bool replace_existing) { - XrActionMap *am_prev = WM_xr_actionmap_find(runtime, name); + XrActionMap *am_prev = WM_xr_actionmap_find(actionconf, name); if (am_prev && replace_existing) { WM_xr_actionmap_clear(am_prev); return am_prev; @@ -390,31 +455,32 @@ XrActionMap *WM_xr_actionmap_new(wmXrRuntimeData *runtime, const char *name, boo 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); + WM_xr_actionmap_ensure_unique(actionconf, am); } - BLI_addtail(&runtime->actionmaps, am); + BLI_addtail(&actionconf->actionmaps, am); + + WM_xr_actionconfig_update_tag(am, NULL); return am; } -static XrActionMap *wm_xr_actionmap_find_except(wmXrRuntimeData *runtime, +static XrActionMap *wm_xr_actionmap_find_except(XrActionConfig *actionconf, const char *name, - const XrActionMap *am_except) + XrActionMap *am_except) { - LISTBASE_FOREACH (XrActionMap *, am, &runtime->actionmaps) { + LISTBASE_FOREACH (XrActionMap *, am, &actionconf->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) +void WM_xr_actionmap_ensure_unique(XrActionConfig *actionconf, XrActionMap *actionmap) { char name[MAX_NAME]; char *suffix; @@ -425,7 +491,7 @@ void WM_xr_actionmap_ensure_unique(wmXrRuntimeData *runtime, XrActionMap *action baselen = BLI_strnlen(name, MAX_NAME); suffix = &name[baselen]; - while (wm_xr_actionmap_find_except(runtime, name, actionmap)) { + while (wm_xr_actionmap_find_except(actionconf, name, actionmap)) { if ((baselen + 1) + (log10(++idx) + 1) > MAX_NAME) { /* Use default base name. */ BLI_strncpy(name, WM_XR_ACTIONMAP_STR_DEFAULT, MAX_NAME); @@ -447,6 +513,7 @@ static XrActionMap *wm_xr_actionmap_copy(XrActionMap *am_src) am_dst->prev = am_dst->next = NULL; BLI_listbase_clear(&am_dst->items); + am_dst->flag &= ~(XR_ACTIONMAP_UPDATE); LISTBASE_FOREACH (XrActionMapItem *, ami, &am_src->items) { XrActionMapItem *ami_new = wm_xr_actionmap_item_copy(ami); @@ -456,33 +523,35 @@ static XrActionMap *wm_xr_actionmap_copy(XrActionMap *am_src) return am_dst; } -XrActionMap *WM_xr_actionmap_add_copy(wmXrRuntimeData *runtime, XrActionMap *am_src) +XrActionMap *WM_xr_actionmap_add_copy(XrActionConfig *actionconf, XrActionMap *am_src) { XrActionMap *am_dst = wm_xr_actionmap_copy(am_src); - WM_xr_actionmap_ensure_unique(runtime, am_dst); + WM_xr_actionmap_ensure_unique(actionconf, am_dst); - BLI_addtail(&runtime->actionmaps, am_dst); + BLI_addtail(&actionconf->actionmaps, am_dst); + + WM_xr_actionconfig_update_tag(am_dst, NULL); return am_dst; } -bool WM_xr_actionmap_remove(wmXrRuntimeData *runtime, XrActionMap *actionmap) +bool WM_xr_actionmap_remove(XrActionConfig *actionconf, XrActionMap *actionmap) { - int idx = BLI_findindex(&runtime->actionmaps, actionmap); + int idx = BLI_findindex(&actionconf->actionmaps, actionmap); if (idx != -1) { WM_xr_actionmap_clear(actionmap); - BLI_freelinkN(&runtime->actionmaps, actionmap); + BLI_freelinkN(&actionconf->actionmaps, actionmap); - if (idx <= runtime->actactionmap) { - if (--runtime->actactionmap < 0) { - runtime->actactionmap = 0; + if (idx <= actionconf->actactionmap) { + if (--actionconf->actactionmap < 0) { + actionconf->actactionmap = 0; } } - if (idx <= runtime->selactionmap) { - if (--runtime->selactionmap < 0) { - runtime->selactionmap = 0; + if (idx <= actionconf->selactionmap) { + if (--actionconf->selactionmap < 0) { + actionconf->selactionmap = 0; } } @@ -492,9 +561,9 @@ bool WM_xr_actionmap_remove(wmXrRuntimeData *runtime, XrActionMap *actionmap) return false; } -XrActionMap *WM_xr_actionmap_find(wmXrRuntimeData *runtime, const char *name) +XrActionMap *WM_xr_actionmap_find(XrActionConfig *actionconf, const char *name) { - LISTBASE_FOREACH (XrActionMap *, am, &runtime->actionmaps) { + LISTBASE_FOREACH (XrActionMap *, am, &actionconf->actionmaps) { if (STREQLEN(name, am->name, MAX_NAME)) { return am; } @@ -502,52 +571,147 @@ XrActionMap *WM_xr_actionmap_find(wmXrRuntimeData *runtime, const char *name) return NULL; } -void WM_xr_actionmap_clear(XrActionMap *actionmap) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Action Configuration + * + * List of XR action maps. + * \{ */ + +XrActionConfig *WM_xr_actionconfig_new(XrSessionSettings *settings, + const char *name, + bool user_defined) { - LISTBASE_FOREACH (XrActionMapItem *, ami, &actionmap->items) { - wm_xr_actionmap_item_bindings_clear(ami); - wm_xr_actionmap_item_properties_free(ami); + XrActionConfig *actionconf = BLI_findstring( + &settings->actionconfigs, name, offsetof(XrActionConfig, name)); + if (actionconf) { + WM_xr_actionconfig_clear(actionconf); + return actionconf; } - BLI_freelistN(&actionmap->items); + actionconf = MEM_callocN(sizeof(XrActionConfig), __func__); + BLI_strncpy(actionconf->name, name, sizeof(actionconf->name)); + BLI_addtail(&settings->actionconfigs, actionconf); + + if (user_defined) { + actionconf->flag |= XR_ACTIONCONF_USER; + } - actionmap->selitem = 0; + return actionconf; } -void WM_xr_actionmaps_clear(wmXrRuntimeData *runtime) +bool WM_xr_actionconfig_remove(XrSessionSettings *settings, XrActionConfig *actionconf) { - LISTBASE_FOREACH (XrActionMap *, am, &runtime->actionmaps) { - WM_xr_actionmap_clear(am); - } + if (BLI_findindex(&settings->actionconfigs, actionconf) != -1) { + if (STREQLEN(g_xr_actionconfigstr, actionconf->name, sizeof(g_xr_actionconfigstr))) { + BLI_strncpy(g_xr_actionconfigstr, settings->defaultconf->name, sizeof(g_xr_actionconfigstr)); + WM_xr_actionconfig_update_tag(NULL, NULL); + } - BLI_freelistN(&runtime->actionmaps); + BLI_remlink(&settings->actionconfigs, actionconf); + WM_xr_actionconfig_free(actionconf); - runtime->actactionmap = runtime->selactionmap = 0; -} + return true; + } -ListBase *WM_xr_actionmaps_get(wmXrRuntimeData *runtime) -{ - return &runtime->actionmaps; + return false; } -short WM_xr_actionmap_active_index_get(const wmXrRuntimeData *runtime) +XrActionConfig *WM_xr_actionconfig_active_get(XrSessionSettings *settings) { - return runtime->actactionmap; + XrActionConfig *actionconf; + + /* First try from preset. */ + actionconf = BLI_findstring( + &settings->actionconfigs, g_xr_actionconfigstr, offsetof(XrActionConfig, name)); + if (actionconf) { + return actionconf; + } + + /* Otherwise use default. */ + return settings->defaultconf; } -void WM_xr_actionmap_active_index_set(wmXrRuntimeData *runtime, short idx) +void WM_xr_actionconfig_active_set(XrSessionSettings *settings, const char *idname) { - runtime->actactionmap = idx; + /* Setting a different action configuration as active: we ensure all is + * updated properly before and after making the change. */ + + WM_xr_actionconfig_update(settings); + + BLI_strncpy(g_xr_actionconfigstr, idname, sizeof(g_xr_actionconfigstr)); + + WM_xr_actionconfig_update_tag(NULL, NULL); + WM_xr_actionconfig_update(settings); } -short WM_xr_actionmap_selected_index_get(const wmXrRuntimeData *runtime) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name XR Action Configuration API + * + * API functions for managing XR action configurations. + * + * \{ */ + +void WM_xr_actionconfig_init(bContext *C) { - return runtime->selactionmap; + wmWindowManager *wm = CTX_wm_manager(C); + XrSessionSettings *settings = &wm->xr.session_settings; + + /* Create standard action configs. */ + if (settings->defaultconf == NULL) { + settings->defaultconf = WM_xr_actionconfig_new(settings, WM_XR_ACTIONCONF_STR_DEFAULT, false); + } + if (settings->addonconf == NULL) { + settings->addonconf = WM_xr_actionconfig_new( + settings, WM_XR_ACTIONCONF_STR_DEFAULT " addon", false); + } + if (settings->userconf == NULL) { + /* Treat user config as user-defined so its actionmaps can be saved to files. */ + settings->userconf = WM_xr_actionconfig_new( + settings, WM_XR_ACTIONCONF_STR_DEFAULT " user", true); + } } -void WM_xr_actionmap_selected_index_set(wmXrRuntimeData *runtime, short idx) +void WM_xr_actionconfig_update(XrSessionSettings *settings) { - runtime->selactionmap = idx; + if (G.background) { + return; + } + + if (g_xr_actionconfig_update_flag == 0) { + return; + } + + const bool ensure = ((g_xr_actionconfig_update_flag & WM_XR_ACTIONCONF_UPDATE_ENSURE) != 0); + + if ((g_xr_actionconfig_update_flag & WM_XR_ACTIONCONF_UPDATE_ALL) != 0) { + /* Update properties for all actionconfigs. */ + LISTBASE_FOREACH (XrActionConfig *, ac, &settings->actionconfigs) { + wm_xr_actionmap_item_properties_update_ot_from_list(&ac->actionmaps, ensure); + } + + g_xr_actionconfig_update_flag &= ~(WM_XR_ACTIONCONF_UPDATE_ALL | + WM_XR_ACTIONCONF_UPDATE_ACTIVE); + } + + if ((g_xr_actionconfig_update_flag & WM_XR_ACTIONCONF_UPDATE_ACTIVE) != 0) { + /* Update properties for active actionconfig. */ + XrActionConfig *ac = WM_xr_actionconfig_active_get(settings); + if (ac) { + wm_xr_actionmap_item_properties_update_ot_from_list(&ac->actionmaps, ensure); + } + + g_xr_actionconfig_update_flag &= ~WM_XR_ACTIONCONF_UPDATE_ACTIVE; + } + + if (ensure) { + g_xr_actionconfig_update_flag &= ~WM_XR_ACTIONCONF_UPDATE_ENSURE; + } + + BLI_assert(g_xr_actionconfig_update_flag == 0); } /** \} */ diff --git a/source/blender/windowmanager/xr/intern/wm_xr_draw.c b/source/blender/windowmanager/xr/intern/wm_xr_draw.c index bbb73fc2007..3287e29cdcf 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_draw.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_draw.c @@ -24,6 +24,8 @@ #include <string.h> +#include "BKE_context.h" + #include "BLI_listbase.h" #include "BLI_math.h" @@ -31,6 +33,9 @@ #include "GHOST_C-api.h" +#include "GPU_batch_presets.h" +#include "GPU_immediate.h" +#include "GPU_matrix.h" #include "GPU_viewport.h" #include "WM_api.h" @@ -44,6 +49,16 @@ void wm_xr_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4]) copy_v3_v3(r_mat[3], pose->position); } +void wm_xr_pose_scale_to_mat(const GHOST_XrPose *pose, float scale, float r_mat[4][4]) +{ + wm_xr_pose_to_mat(pose, r_mat); + + BLI_assert(scale > 0.0f); + mul_v3_fl(r_mat[0], scale); + mul_v3_fl(r_mat[1], scale); + mul_v3_fl(r_mat[2], scale); +} + void wm_xr_pose_to_imat(const GHOST_XrPose *pose, float r_imat[4][4]) { float iquat[4]; @@ -52,15 +67,33 @@ void wm_xr_pose_to_imat(const GHOST_XrPose *pose, float r_imat[4][4]) translate_m4(r_imat, -pose->position[0], -pose->position[1], -pose->position[2]); } +void wm_xr_pose_scale_to_imat(const GHOST_XrPose *pose, float scale, float r_imat[4][4]) +{ + float iquat[4]; + invert_qt_qt_normalized(iquat, pose->orientation_quat); + quat_to_mat4(r_imat, iquat); + + BLI_assert(scale > 0.0f); + scale = 1.0f / scale; + mul_v3_fl(r_imat[0], scale); + mul_v3_fl(r_imat[1], scale); + mul_v3_fl(r_imat[2], scale); + + translate_m4(r_imat, -pose->position[0], -pose->position[1], -pose->position[2]); +} + static void wm_xr_draw_matrices_create(const wmXrDrawData *draw_data, const GHOST_XrDrawViewInfo *draw_view, const XrSessionSettings *session_settings, - float r_view_mat[4][4], - float r_proj_mat[4][4]) + 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]; + 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); copy_v3_v3(eye_pose.position, draw_view->eye_pose.position); if ((session_settings->flag & XR_SESSION_USE_POSITION_TRACKING) == 0) { @@ -69,14 +102,15 @@ static void wm_xr_draw_matrices_create(const wmXrDrawData *draw_data, if ((session_settings->flag & XR_SESSION_USE_ABSOLUTE_TRACKING) == 0) { sub_v3_v3(eye_pose.position, draw_data->eye_position_ofs); } - wm_xr_pose_to_imat(&eye_pose, eye_inv); - /* Calculate the base pose matrix (in world space!). */ - wm_xr_pose_to_imat(&draw_data->base_pose, base_inv); - mul_m4_m4m4(r_view_mat, eye_inv, base_inv); + /* 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(r_viewmat_base, eye_inv, base_inv); + mul_m4_m4m4(r_viewmat, r_viewmat_base, nav_inv); - perspective_m4_fov(r_proj_mat, + perspective_m4_fov(r_projmat, draw_view->fov.angle_left, draw_view->fov.angle_right, draw_view->fov.angle_up, @@ -121,13 +155,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, 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; @@ -153,6 +188,7 @@ void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata) winmat, settings->clip_start, settings->clip_end, + true, false, true, NULL, @@ -172,3 +208,200 @@ void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata) wm_xr_draw_viewport_buffers_to_active_framebuffer(xr_data->runtime, surface_data, draw_view); } + +static GPUBatch *wm_xr_controller_model_batch_create(GHOST_XrContextHandle xr_context, + const char *subaction_path) +{ + GHOST_XrControllerModelData model_data; + + if (!GHOST_XrGetControllerModelData(xr_context, subaction_path, &model_data) || + model_data.count_vertices < 1) { + return NULL; + } + + GPUVertFormat format = {0}; + GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + GPU_vertformat_attr_add(&format, "nor", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + + GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format); + GPU_vertbuf_data_alloc(vbo, model_data.count_vertices); + void *vbo_data = GPU_vertbuf_get_data(vbo); + memcpy( + vbo_data, model_data.vertices, model_data.count_vertices * sizeof(model_data.vertices[0])); + + GPUIndexBuf *ibo = NULL; + if (model_data.count_indices > 0 && ((model_data.count_indices % 3) == 0)) { + GPUIndexBufBuilder ibo_builder; + const unsigned int prim_len = model_data.count_indices / 3; + GPU_indexbuf_init(&ibo_builder, GPU_PRIM_TRIS, prim_len, model_data.count_vertices); + for (unsigned int i = 0; i < prim_len; ++i) { + const uint32_t *idx = &model_data.indices[i * 3]; + GPU_indexbuf_add_tri_verts(&ibo_builder, idx[0], idx[1], idx[2]); + } + ibo = GPU_indexbuf_build(&ibo_builder); + } + + return GPU_batch_create_ex(GPU_PRIM_TRIS, vbo, ibo, GPU_BATCH_OWNS_VBO | GPU_BATCH_OWNS_INDEX); +} + +static void wm_xr_controller_model_draw(const XrSessionSettings *settings, + GHOST_XrContextHandle xr_context, + wmXrSessionState *state) +{ + GHOST_XrControllerModelData model_data; + + float color[4]; + switch (settings->controller_draw_style) { + case XR_CONTROLLER_DRAW_DARK: + case XR_CONTROLLER_DRAW_DARK_RAY: + color[0] = color[1] = color[2] = 0.0f, color[3] = 0.4f; + break; + case XR_CONTROLLER_DRAW_LIGHT: + case XR_CONTROLLER_DRAW_LIGHT_RAY: + color[0] = 0.422f, color[1] = 0.438f, color[2] = 0.446f, color[3] = 0.4f; + break; + } + + GPU_depth_test(GPU_DEPTH_NONE); + GPU_blend(GPU_BLEND_ALPHA); + + LISTBASE_FOREACH (wmXrController *, controller, &state->controllers) { + GPUBatch *model = controller->model; + if (!model) { + model = controller->model = wm_xr_controller_model_batch_create(xr_context, + controller->subaction_path); + } + + if (model && + GHOST_XrGetControllerModelData(xr_context, controller->subaction_path, &model_data) && + model_data.count_components > 0) { + GPU_batch_program_set_builtin(model, GPU_SHADER_3D_UNIFORM_COLOR); + GPU_batch_uniform_4fv(model, "color", color); + + GPU_matrix_push(); + GPU_matrix_mul(controller->grip_mat); + for (unsigned int component_idx = 0; component_idx < model_data.count_components; + ++component_idx) { + const GHOST_XrControllerModelComponent *component = &model_data.components[component_idx]; + GPU_matrix_push(); + GPU_matrix_mul(component->transform); + GPU_batch_draw_range(model, + model->elem ? component->index_offset : component->vertex_offset, + model->elem ? component->index_count : component->vertex_count); + GPU_matrix_pop(); + } + GPU_matrix_pop(); + } + else { + /* Fallback. */ + const float scale = 0.05f; + GPUBatch *sphere = GPU_batch_preset_sphere(2); + GPU_batch_program_set_builtin(sphere, GPU_SHADER_3D_UNIFORM_COLOR); + GPU_batch_uniform_4fv(sphere, "color", color); + + GPU_matrix_push(); + GPU_matrix_mul(controller->grip_mat); + GPU_matrix_scale_1f(scale); + GPU_batch_draw(sphere); + GPU_matrix_pop(); + } + } +} + +static void wm_xr_controller_aim_draw(const XrSessionSettings *settings, wmXrSessionState *state) +{ + bool draw_ray; + switch (settings->controller_draw_style) { + case XR_CONTROLLER_DRAW_DARK: + case XR_CONTROLLER_DRAW_LIGHT: + draw_ray = false; + break; + case XR_CONTROLLER_DRAW_DARK_RAY: + case XR_CONTROLLER_DRAW_LIGHT_RAY: + draw_ray = true; + break; + } + + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT); + immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_FLAT_COLOR); + + float viewport[4]; + GPU_viewport_size_get_f(viewport); + immUniform2fv("viewportSize", &viewport[2]); + + immUniform1f("lineWidth", 3.0f * U.pixelsize); + + if (draw_ray) { + const uchar color[4] = {89, 89, 255, 127}; + const float scale = settings->clip_end; + float ray[3]; + + GPU_depth_test(GPU_DEPTH_LESS_EQUAL); + GPU_blend(GPU_BLEND_ALPHA); + + immBegin(GPU_PRIM_LINES, (uint)BLI_listbase_count(&state->controllers) * 2); + + LISTBASE_FOREACH (wmXrController *, controller, &state->controllers) { + const float(*mat)[4] = controller->aim_mat; + madd_v3_v3v3fl(ray, mat[3], mat[2], -scale); + + immAttrSkip(col); + immVertex3fv(pos, mat[3]); + immAttr4ubv(col, color); + immVertex3fv(pos, ray); + } + + immEnd(); + } + else { + const uchar r[4] = {255, 51, 82, 255}; + const uchar g[4] = {139, 220, 0, 255}; + const uchar b[4] = {40, 144, 255, 255}; + const float scale = 0.01f; + float x_axis[3], y_axis[3], z_axis[3]; + + GPU_depth_test(GPU_DEPTH_NONE); + GPU_blend(GPU_BLEND_NONE); + + immBegin(GPU_PRIM_LINES, (uint)BLI_listbase_count(&state->controllers) * 6); + + LISTBASE_FOREACH (wmXrController *, controller, &state->controllers) { + const float(*mat)[4] = controller->aim_mat; + madd_v3_v3v3fl(x_axis, mat[3], mat[0], scale); + madd_v3_v3v3fl(y_axis, mat[3], mat[1], scale); + madd_v3_v3v3fl(z_axis, mat[3], mat[2], scale); + + immAttrSkip(col); + immVertex3fv(pos, mat[3]); + immAttr4ubv(col, r); + immVertex3fv(pos, x_axis); + + immAttrSkip(col); + immVertex3fv(pos, mat[3]); + immAttr4ubv(col, g); + immVertex3fv(pos, y_axis); + + immAttrSkip(col); + immVertex3fv(pos, mat[3]); + immAttr4ubv(col, b); + immVertex3fv(pos, z_axis); + } + + immEnd(); + } + + immUnbindProgram(); +} + +void wm_xr_draw_controllers(const bContext *UNUSED(C), ARegion *UNUSED(region), void *customdata) +{ + wmXrData *xr = customdata; + const XrSessionSettings *settings = &xr->session_settings; + GHOST_XrContextHandle xr_context = xr->runtime->context; + wmXrSessionState *state = &xr->runtime->session_state; + + wm_xr_controller_model_draw(settings, xr_context, state); + wm_xr_controller_aim_draw(settings, state); +} diff --git a/source/blender/windowmanager/xr/intern/wm_xr_intern.h b/source/blender/windowmanager/xr/intern/wm_xr_intern.h index fc54e261f79..6fce270643f 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_intern.h +++ b/source/blender/windowmanager/xr/intern/wm_xr_intern.h @@ -20,10 +20,23 @@ #pragma once +#include "BLI_sys_types.h" + #include "CLG_log.h" #include "wm_xr.h" +struct ARegion; +struct ARegionType; +struct bContext; +struct bScreen; +struct Depsgraph; +struct GPUBatch; +struct GPUOffScreen; +struct GPUViewport; +struct Scene; +struct ViewLayer; +struct wmOperatorType; struct wmXrActionSet; typedef struct wmXrSessionState { @@ -31,18 +44,25 @@ typedef struct wmXrSessionState { /** Last known viewer pose (centroid of eyes, in world space) stored for queries. */ GHOST_XrPose viewer_pose; - /** The last known view matrix, calculated from above's viewer pose. */ + /** The last known view matrix (inverse viewer matrix), calculated from above's viewer pose. */ float viewer_viewmat[4][4]; - float focal_len; + /** The last known viewer matrix, without navigation applied. */ + float viewer_mat_base[4][4]; + /** Last known eye data. */ + ListBase eyes; /* wmXrEye */ + /** Last known controller data. */ + ListBase controllers; /* wmXrController */ /** Copy of XrSessionSettings.base_pose_ data to detect changes that need * resetting to base pose. */ - char prev_base_pose_type; /* eXRSessionBasePoseType */ + char prev_base_pose_type; /* eXrSessionBasePoseType */ Object *prev_base_pose_object; /** Copy of XrSessionSettings.flag created on the last draw call, stored to detect changes. */ int prev_settings_flag; /** Copy of wmXrDrawData.base_pose. */ GHOST_XrPose prev_base_pose; + /** Copy of wmXrDrawData.base_scale. */ + float prev_base_scale; /** Copy of GHOST_XrDrawViewInfo.local_pose. */ GHOST_XrPose prev_local_pose; /** Copy of wmXrDrawData.eye_position_ofs. */ @@ -51,13 +71,23 @@ typedef struct wmXrSessionState { bool force_reset_to_base_pose; bool is_view_data_set; - /** Last known controller data. */ - ListBase controllers; /* wmXrController */ + /** Current navigation transforms. */ + GHOST_XrPose nav_pose; + float nav_scale; + /** Navigation transforms from the last actions sync, used to calculate the viewer/controller + * poses. */ + GHOST_XrPose nav_pose_prev; + float nav_scale_prev; + bool is_navigation_dirty; /** The currently active action set that will be updated on calls to * wm_xr_session_actions_update(). If NULL, all action sets will be treated as active and * updated. */ struct wmXrActionSet *active_action_set; + + /* Original poses for motion capture objects. Used to properly restore object transforms on + * session end. */ + ListBase mocap_orig_poses; /* wmXrMotionCapturePose */ } wmXrSessionState; typedef struct wmXrRuntimeData { @@ -70,10 +100,6 @@ 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 { @@ -85,6 +111,11 @@ typedef struct wmXrViewportPair { typedef struct { /** Off-screen buffers/viewports for each view. */ ListBase viewports; /* wmXrViewportPair */ + + /** Dummy region type for controller draw callback. */ + struct ARegionType *controller_art; + /** Controller draw callback handle. */ + void *controller_draw_handle; } wmXrSurfaceData; typedef struct wmXrDrawData { @@ -98,11 +129,21 @@ typedef struct wmXrDrawData { * space). With positional tracking enabled, it should be the same as the base pose, when * disabled it also contains a location delta from the moment the option was toggled. */ GHOST_XrPose base_pose; + /** Base scale (uniform, world space). */ + float base_scale; /** Offset to _substract_ from the OpenXR eye and viewer pose to get the wanted effective pose * (e.g. a pose exactly at the landmark position). */ 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 path identifier. Length is dependent on OpenXR's XR_MAX_PATH_LENGTH (256). @@ -111,12 +152,18 @@ typedef struct wmXrController { /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]; + float grip_mat_base[4][4]; /* Pose (in world space) that represents the controller's aiming source. */ GHOST_XrPose aim_pose; float aim_mat[4][4]; + float aim_mat_base[4][4]; + + /** Controller model. */ + struct GPUBatch *model; } wmXrController; typedef struct wmXrAction { @@ -167,22 +214,32 @@ typedef struct wmXrActionSet { wmXrAction *controller_aim_action; /** Currently active modal actions. */ - ListBase active_modal_actions; + ListBase active_modal_actions; /* wmXrAction */ /** Currently active haptic actions. */ - ListBase active_haptic_actions; + ListBase active_haptic_actions; /* wmXrHapticAction */ } wmXrActionSet; +typedef struct wmXrMotionCapturePose { + struct wmXrMotionCapturePose *next, *prev; + const Object *ob; + GHOST_XrPose pose; +} wmXrMotionCapturePose; + +/* wm_xr.c */ wmXrRuntimeData *wm_xr_runtime_data_create(void); void wm_xr_runtime_data_free(wmXrRuntimeData **runtime); -void wm_xr_session_data_free(wmXrSessionState *state); -void wm_xr_session_draw_data_update(const wmXrSessionState *state, +/* wm_xr_session.c */ +void wm_xr_session_data_free(wmXrSessionState *state); +void wm_xr_session_draw_data_update(wmXrSessionState *state, const XrSessionSettings *settings, const GHOST_XrDrawViewInfo *draw_view, wmXrDrawData *draw_data); 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); @@ -190,12 +247,36 @@ void *wm_xr_session_gpu_binding_context_create(void); void wm_xr_session_gpu_binding_context_destroy(GHOST_ContextHandle context); void wm_xr_session_actions_init(wmXrData *xr); -void wm_xr_session_actions_update(wmXrData *xr); +void wm_xr_session_actions_update(const struct bContext *C); void wm_xr_session_controller_data_populate(const wmXrAction *grip_action, const wmXrAction *aim_action, wmXrData *xr); void wm_xr_session_controller_data_clear(wmXrSessionState *state); +/* wm_xr_draw.c */ void wm_xr_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4]); +void wm_xr_pose_scale_to_mat(const GHOST_XrPose *pose, float scale, float r_mat[4][4]); void wm_xr_pose_to_imat(const GHOST_XrPose *pose, float r_imat[4][4]); +void wm_xr_pose_scale_to_imat(const GHOST_XrPose *pose, float scale, float r_imat[4][4]); + void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata); +void wm_xr_draw_controllers(const struct bContext *C, struct ARegion *region, void *customdata); + +/* wm_xr_mocap.c */ +void wm_xr_mocap_orig_poses_store(const XrSessionSettings *settings, wmXrSessionState *state); +void wm_xr_mocap_orig_poses_restore(const wmXrSessionState *state, XrSessionSettings *settings); +void wm_xr_mocap_object_autokey(struct bContext *C, + struct Scene *scene, + struct ViewLayer *view_layer, + wmWindow *win, + Object *ob, + bool notify); +void wm_xr_mocap_objects_update(const char *user_path, + const GHOST_XrPose *pose, + struct bContext *C, + XrSessionSettings *settings, + struct Scene *scene, + struct ViewLayer *view_layer, + wmWindow *win, + struct bScreen *screen_anim, + bool notify); diff --git a/source/blender/windowmanager/xr/intern/wm_xr_mocap.c b/source/blender/windowmanager/xr/intern/wm_xr_mocap.c new file mode 100644 index 00000000000..e7dde563225 --- /dev/null +++ b/source/blender/windowmanager/xr/intern/wm_xr_mocap.c @@ -0,0 +1,267 @@ +/* + * 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 Motion Capture + * + * API for XR motion capture objects. + */ + +#include "BKE_context.h" +#include "BKE_layer.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" + +#include "DEG_depsgraph.h" + +#include "DNA_object_types.h" + +#include "ED_keyframing.h" +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_transform.h" + +#include "GHOST_C-api.h" + +#include "MEM_guardedalloc.h" + +#include "WM_api.h" + +#include "wm_xr_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name XR Motion Capture Objects + * + * List of XR motion capture objects. Stored in session settings and can be written to files. + * \{ */ + +XrMotionCaptureObject *WM_xr_mocap_object_new(XrSessionSettings *settings, Object *ob) +{ + XrMotionCaptureObject *mocap_ob = WM_xr_mocap_object_find(settings, ob); + + if (!mocap_ob) { + mocap_ob = MEM_callocN(sizeof(XrMotionCaptureObject), __func__); + mocap_ob->ob = ob; + BLI_addtail(&settings->mocap_objects, mocap_ob); + } + + return mocap_ob; +} + +void WM_xr_mocap_object_ensure_unique(XrSessionSettings *settings, XrMotionCaptureObject *mocap_ob) +{ + LISTBASE_FOREACH (XrMotionCaptureObject *, mocap_ob_other, &settings->mocap_objects) { + if ((mocap_ob_other != mocap_ob) && (mocap_ob_other->ob == mocap_ob->ob)) { + mocap_ob->ob = NULL; + return; + } + } +} + +void WM_xr_mocap_object_remove(XrSessionSettings *settings, XrMotionCaptureObject *mocap_ob) +{ + int idx = BLI_findindex(&settings->mocap_objects, mocap_ob); + + if (idx != -1) { + BLI_freelinkN(&settings->mocap_objects, mocap_ob); + + if (idx <= settings->sel_mocap_object) { + if (--settings->sel_mocap_object < 0) { + settings->sel_mocap_object = 0; + } + } + } +} + +XrMotionCaptureObject *WM_xr_mocap_object_find(const XrSessionSettings *settings, const Object *ob) +{ + return ob ? BLI_findptr(&settings->mocap_objects, ob, offsetof(XrMotionCaptureObject, ob)) : + NULL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Motion Capture Runtime + * + * Runtime functions for motion capture object poses and autokeying. + * \{ */ + +static void wm_xr_mocap_object_pose_get(const XrMotionCaptureObject *mocap_ob, GHOST_XrPose *pose) +{ + BLI_assert(mocap_ob->ob); + copy_v3_v3(pose->position, mocap_ob->ob->loc); + eul_to_quat(pose->orientation_quat, mocap_ob->ob->rot); +} + +static void wm_xr_mocap_object_pose_set(const GHOST_XrPose *pose, + XrMotionCaptureObject *mocap_ob, + bool apply_offset) +{ + BLI_assert(mocap_ob->ob); + + if (apply_offset) { + /* Convert offsets to pose (device) space. */ + float loc_ofs[3], rot_ofs[4]; + copy_v3_v3(loc_ofs, mocap_ob->location_offset); + mul_qt_v3(pose->orientation_quat, loc_ofs); + + eul_to_quat(rot_ofs, mocap_ob->rotation_offset); + normalize_qt(rot_ofs); + invert_qt_normalized(rot_ofs); + mul_qt_qtqt(rot_ofs, pose->orientation_quat, rot_ofs); + normalize_qt(rot_ofs); + + add_v3_v3v3(mocap_ob->ob->loc, pose->position, loc_ofs); + quat_to_eul(mocap_ob->ob->rot, rot_ofs); + } + else { + copy_v3_v3(mocap_ob->ob->loc, pose->position); + quat_to_eul(mocap_ob->ob->rot, pose->orientation_quat); + } + + DEG_id_tag_update(&mocap_ob->ob->id, ID_RECALC_TRANSFORM); +} + +void wm_xr_mocap_orig_poses_store(const XrSessionSettings *settings, wmXrSessionState *state) +{ + ListBase *mocap_orig_poses = &state->mocap_orig_poses; + + LISTBASE_FOREACH (XrMotionCaptureObject *, mocap_ob, &settings->mocap_objects) { + wmXrMotionCapturePose *mocap_pose = MEM_callocN(sizeof(wmXrMotionCapturePose), __func__); + mocap_pose->ob = mocap_ob->ob; + if (mocap_ob->ob) { + wm_xr_mocap_object_pose_get(mocap_ob, &mocap_pose->pose); + } + BLI_addtail(mocap_orig_poses, mocap_pose); + } +} + +void wm_xr_mocap_orig_poses_restore(const wmXrSessionState *state, XrSessionSettings *settings) +{ + ListBase *mocap_objects = &settings->mocap_objects; + + LISTBASE_FOREACH (wmXrMotionCapturePose *, mocap_pose, &state->mocap_orig_poses) { + XrMotionCaptureObject *mocap_ob = BLI_findptr( + mocap_objects, mocap_pose->ob, offsetof(XrMotionCaptureObject, ob)); + if (mocap_ob && mocap_ob->ob) { + wm_xr_mocap_object_pose_set(&mocap_pose->pose, mocap_ob, false); + } + } +} + +void wm_xr_mocap_object_autokey( + bContext *C, Scene *scene, ViewLayer *view_layer, wmWindow *win, Object *ob, bool notify) +{ + /* Poll functions in keyingsets_utils.py require an active window and object. */ + wmWindow *win_prev = win ? CTX_wm_window(C) : NULL; + if (win) { + CTX_wm_window_set(C, win); + } + + Object *obact = CTX_data_active_object(C); + if (!obact) { + Base *base = BKE_view_layer_base_find(view_layer, ob); + if (base) { + ED_object_base_select(base, BA_SELECT); + ED_object_base_activate(C, base); + } + } + + bScreen *screen = CTX_wm_screen(C); + if (screen && screen->animtimer && (IS_AUTOKEY_FLAG(scene, INSERTAVAIL) == 0) && + ((scene->toolsettings->autokey_flag & ANIMRECORD_FLAG_WITHNLA) != 0)) { + ED_transform_animrecord_check_state(scene, screen->animtimer, ob); + } + + ED_transform_autokeyframe_object(C, scene, view_layer, ob, TFM_TRANSLATION); + if (IS_AUTOKEY_FLAG(scene, INSERTNEEDED)) { + ED_transform_autokeyframe_object(C, scene, view_layer, ob, TFM_ROTATION); + } + + if (ED_transform_motionpath_need_update_object(scene, ob)) { + ED_objects_recalculate_paths(C, scene, OBJECT_PATH_CALC_RANGE_CURRENT_FRAME); + } + + if (notify) { + WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, NULL); + WM_main_add_notifier(NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); + } + + if (win) { + CTX_wm_window_set(C, win_prev); + } +} + +void wm_xr_mocap_objects_update(const char *user_path, + const GHOST_XrPose *pose, + bContext *C, + XrSessionSettings *settings, + Scene *scene, + ViewLayer *view_layer, + wmWindow *win, + bScreen *screen_anim, + bool notify) +{ + LISTBASE_FOREACH (XrMotionCaptureObject *, mocap_ob, &settings->mocap_objects) { + if (mocap_ob->ob && ((mocap_ob->flag & XR_MOCAP_OBJECT_ENABLE) != 0) && + STREQ(mocap_ob->user_path, user_path)) { + wm_xr_mocap_object_pose_set(pose, mocap_ob, true); + + if (((mocap_ob->flag & XR_MOCAP_OBJECT_AUTOKEY) != 0) && screen_anim && + autokeyframe_cfra_can_key(scene, &mocap_ob->ob->id)) { + wm_xr_mocap_object_autokey(C, scene, view_layer, win, mocap_ob->ob, notify); + notify = false; + } + } + } +} + +bool WM_xr_session_state_mocap_pose_get(const wmXrData *xr, XrMotionCaptureObject *mocap_ob) +{ + if (!WM_xr_session_exists(xr)) { + return false; + } + + if (mocap_ob->ob) { + wmXrMotionCapturePose *mocap_pose = BLI_findptr(&xr->runtime->session_state.mocap_orig_poses, + mocap_ob->ob, + offsetof(wmXrMotionCapturePose, ob)); + if (!mocap_pose) { + return false; + } + wm_xr_mocap_object_pose_set(&mocap_pose->pose, mocap_ob, false); + } + + return true; +} + +void WM_xr_session_state_mocap_pose_set(wmXrData *xr, const XrMotionCaptureObject *mocap_ob) +{ + if (WM_xr_session_exists(xr) && mocap_ob->ob) { + wmXrMotionCapturePose *mocap_pose = BLI_findptr(&xr->runtime->session_state.mocap_orig_poses, + mocap_ob->ob, + offsetof(wmXrMotionCapturePose, ob)); + if (mocap_pose) { + wm_xr_mocap_object_pose_get(mocap_ob, &mocap_pose->pose); + } + } +} + +/** \} */ diff --git a/source/blender/windowmanager/xr/intern/wm_xr_operators.c b/source/blender/windowmanager/xr/intern/wm_xr_operators.c new file mode 100644 index 00000000000..4f7499f3aa0 --- /dev/null +++ b/source/blender/windowmanager/xr/intern/wm_xr_operators.c @@ -0,0 +1,2492 @@ +/* + * 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 Operators + * + * Collection of XR-related operators. + */ + +#include "BLI_kdopbvh.h" +#include "BLI_listbase.h" +#include "BLI_math.h" + +#include "BKE_context.h" +#include "BKE_editmesh.h" +#include "BKE_global.h" +#include "BKE_idprop.h" +#include "BKE_layer.h" +#include "BKE_main.h" +#include "BKE_object.h" +#include "BKE_screen.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "ED_keyframing.h" +#include "ED_mesh.h" +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_select_utils.h" +#include "ED_space_api.h" +#include "ED_transform_snap_object_context.h" +#include "ED_view3d.h" + +#include "GHOST_Types.h" + +#include "GPU_immediate.h" + +#include "MEM_guardedalloc.h" + +#include "PIL_time.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "wm_xr_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Operator Conditions + * \{ */ + +/* op->poll */ +static bool wm_xr_operator_sessionactive(bContext *C) +{ + wmWindowManager *wm = CTX_wm_manager(C); + return WM_xr_session_is_ready(&wm->xr); +} + +static bool wm_xr_operator_test_event(const wmOperator *op, const wmEvent *event) +{ + if (event->type != EVT_XR_ACTION) { + return false; + } + + BLI_assert(event->custom == EVT_DATA_XR); + BLI_assert(event->customdata); + + wmXrActionData *actiondata = event->customdata; + return (actiondata->ot == op->type && + IDP_EqualsProperties(actiondata->op_properties, op->properties)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name XR Session Toggle + * + * Toggles an XR session, creating an XR context if necessary. + * \{ */ + +static void wm_xr_session_update_screen(Main *bmain, const wmXrData *xr_data) +{ + const bool session_exists = WM_xr_session_exists(xr_data); + + for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, slink, &area->spacedata) { + if (slink->spacetype == SPACE_VIEW3D) { + View3D *v3d = (View3D *)slink; + + if (v3d->flag & V3D_XR_SESSION_MIRROR) { + ED_view3d_xr_mirror_update(area, v3d, session_exists); + } + + if (session_exists) { + wmWindowManager *wm = bmain->wm.first; + const Scene *scene = WM_windows_scene_get_from_screen(wm, screen); + + ED_view3d_xr_shading_update(wm, v3d, scene); + } + /* Ensure no 3D View is tagged as session root. */ + else { + v3d->runtime.flag &= ~V3D_RUNTIME_XR_SESSION_ROOT; + } + } + } + } + } + + WM_main_add_notifier(NC_WM | ND_XR_DATA_CHANGED, NULL); +} + +static void wm_xr_session_update_screen_on_exit_cb(const wmXrData *xr_data) +{ + /* Just use G_MAIN here, storing main isn't reliable enough on file read or exit. */ + wm_xr_session_update_screen(G_MAIN, xr_data); +} + +static int wm_xr_session_toggle_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Main *bmain = CTX_data_main(C); + wmWindowManager *wm = CTX_wm_manager(C); + wmWindow *win = CTX_wm_window(C); + View3D *v3d = CTX_wm_view3d(C); + + /* Lazy-create xr context - tries to dynlink to the runtime, reading active_runtime.json. */ + if (wm_xr_init(wm) == false) { + return OPERATOR_CANCELLED; + } + + v3d->runtime.flag |= V3D_RUNTIME_XR_SESSION_ROOT; + wm_xr_session_toggle(wm, win, wm_xr_session_update_screen_on_exit_cb); + wm_xr_session_update_screen(bmain, &wm->xr); + + WM_event_add_notifier(C, NC_WM | ND_XR_DATA_CHANGED, NULL); + + return OPERATOR_FINISHED; +} + +static void WM_OT_xr_session_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Toggle VR Session"; + ot->idname = "WM_OT_xr_session_toggle"; + ot->description = + "Open a view for use with virtual reality headsets, or close it if already " + "opened"; + + /* callbacks */ + ot->exec = wm_xr_session_toggle_exec; + ot->poll = ED_operator_view3d_active; + + /* XXX INTERNAL just to hide it from the search menu by default, an Add-on will expose it in the + * UI instead. Not meant as a permanent solution. */ + ot->flag = OPTYPE_INTERNAL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name XR Grab Utilities + * \{ */ + +typedef struct XrGrabData { + float mat_prev[4][4]; + float mat_other_prev[4][4]; + bool bimanual_prev; +} XrGrabData; + +static void wm_xr_grab_init(wmOperator *op) +{ + BLI_assert(op->customdata == NULL); + + op->customdata = MEM_callocN(sizeof(XrGrabData), __func__); +} + +static void wm_xr_grab_uninit(wmOperator *op) +{ + MEM_SAFE_FREE(op->customdata); +} + +static void wm_xr_grab_update(wmOperator *op, const wmXrActionData *actiondata) +{ + XrGrabData *data = op->customdata; + + quat_to_mat4(data->mat_prev, actiondata->controller_rot); + copy_v3_v3(data->mat_prev[3], actiondata->controller_loc); + + if (actiondata->bimanual) { + quat_to_mat4(data->mat_other_prev, actiondata->controller_rot_other); + copy_v3_v3(data->mat_other_prev[3], actiondata->controller_loc_other); + data->bimanual_prev = true; + } + else { + data->bimanual_prev = false; + } +} + +static void orient_mat_z_normalized(float R[4][4], const float z_axis[3]) +{ + const float scale = len_v3(R[0]); + float x_axis[3], y_axis[3]; + + cross_v3_v3v3(y_axis, z_axis, R[0]); + normalize_v3(y_axis); + mul_v3_v3fl(R[1], y_axis, scale); + + cross_v3_v3v3(x_axis, R[1], z_axis); + normalize_v3(x_axis); + mul_v3_v3fl(R[0], x_axis, scale); + + mul_v3_v3fl(R[2], z_axis, scale); +} + +static void wm_xr_navlocks_apply(const float nav_mat[4][4], + const float nav_inv[4][4], + bool loc_lock, + bool locz_lock, + bool rotz_lock, + float r_prev[4][4], + float r_curr[4][4]) +{ + /* Locked in base pose coordinates. */ + float prev_base[4][4], curr_base[4][4]; + + mul_m4_m4m4(prev_base, nav_inv, r_prev); + mul_m4_m4m4(curr_base, nav_inv, r_curr); + + if (rotz_lock) { + const float z_axis[3] = {0.0f, 0.0f, 1.0f}; + orient_mat_z_normalized(prev_base, z_axis); + orient_mat_z_normalized(curr_base, z_axis); + } + + if (loc_lock) { + copy_v3_v3(curr_base[3], prev_base[3]); + } + else if (locz_lock) { + curr_base[3][2] = prev_base[3][2]; + } + + mul_m4_m4m4(r_prev, nav_mat, prev_base); + mul_m4_m4m4(r_curr, nav_mat, curr_base); +} + +static void wm_xr_grab_compute(const wmXrActionData *actiondata, + const XrGrabData *data, + const Object *obedit, + const float nav_mat[4][4], + const float nav_inv[4][4], + bool reverse, + bool loc_lock, + bool locz_lock, + bool rot_lock, + bool rotz_lock, + float r_delta[4][4]) +{ + const bool nav_lock = (nav_mat && nav_inv); + float prev[4][4], curr[4][4]; + + if (!rot_lock) { + copy_m4_m4(prev, data->mat_prev); + zero_v3(prev[3]); + quat_to_mat4(curr, actiondata->controller_rot); + } + else { + unit_m4(prev); + unit_m4(curr); + } + + if (!loc_lock || nav_lock) { + copy_v3_v3(prev[3], data->mat_prev[3]); + copy_v3_v3(curr[3], actiondata->controller_loc); + } + + if (obedit) { + mul_m4_m4m4(prev, obedit->imat, prev); + mul_m4_m4m4(curr, obedit->imat, curr); + } + + if (nav_lock) { + wm_xr_navlocks_apply(nav_mat, nav_inv, loc_lock, locz_lock, rotz_lock, prev, curr); + } + + if (reverse) { + invert_m4(curr); + mul_m4_m4m4(r_delta, prev, curr); + } + else { + invert_m4(prev); + mul_m4_m4m4(r_delta, curr, prev); + } +} + +static void wm_xr_grab_compute_bimanual(const wmXrActionData *actiondata, + const XrGrabData *data, + const Object *obedit, + const float nav_mat[4][4], + const float nav_inv[4][4], + bool reverse, + bool loc_lock, + bool locz_lock, + bool rot_lock, + bool rotz_lock, + bool scale_lock, + float r_delta[4][4]) +{ + const bool nav_lock = (nav_mat && nav_inv); + float prev[4][4], curr[4][4]; + unit_m4(prev); + unit_m4(curr); + + if (!rot_lock) { + /* Rotation. */ + float x_axis_prev[3], x_axis_curr[3], y_axis_prev[3], y_axis_curr[3], z_axis_prev[3], + z_axis_curr[3]; + float m0[3][3], m1[3][3]; + quat_to_mat3(m0, actiondata->controller_rot); + quat_to_mat3(m1, actiondata->controller_rot_other); + + /* x-axis is the base line between the two controllers. */ + sub_v3_v3v3(x_axis_prev, data->mat_prev[3], data->mat_other_prev[3]); + sub_v3_v3v3(x_axis_curr, actiondata->controller_loc, actiondata->controller_loc_other); + /* y-axis is the average of the controllers' y-axes. */ + add_v3_v3v3(y_axis_prev, data->mat_prev[1], data->mat_other_prev[1]); + mul_v3_fl(y_axis_prev, 0.5f); + add_v3_v3v3(y_axis_curr, m0[1], m1[1]); + mul_v3_fl(y_axis_curr, 0.5f); + /* z-axis is the cross product of the two. */ + cross_v3_v3v3(z_axis_prev, x_axis_prev, y_axis_prev); + cross_v3_v3v3(z_axis_curr, x_axis_curr, y_axis_curr); + /* Fix the y-axis to be orthogonal. */ + cross_v3_v3v3(y_axis_prev, z_axis_prev, x_axis_prev); + cross_v3_v3v3(y_axis_curr, z_axis_curr, x_axis_curr); + /* Normalize. */ + normalize_v3_v3(prev[0], x_axis_prev); + normalize_v3_v3(prev[1], y_axis_prev); + normalize_v3_v3(prev[2], z_axis_prev); + normalize_v3_v3(curr[0], x_axis_curr); + normalize_v3_v3(curr[1], y_axis_curr); + normalize_v3_v3(curr[2], z_axis_curr); + } + + if (!loc_lock || nav_lock) { + /* Translation: translation of the averaged controller locations. */ + add_v3_v3v3(prev[3], data->mat_prev[3], data->mat_other_prev[3]); + mul_v3_fl(prev[3], 0.5f); + add_v3_v3v3(curr[3], actiondata->controller_loc, actiondata->controller_loc_other); + mul_v3_fl(curr[3], 0.5f); + } + + if (!scale_lock) { + /* Scaling: distance between controllers. */ + float scale, v[3]; + + sub_v3_v3v3(v, data->mat_prev[3], data->mat_other_prev[3]); + scale = len_v3(v); + mul_v3_fl(prev[0], scale); + mul_v3_fl(prev[1], scale); + mul_v3_fl(prev[2], scale); + + sub_v3_v3v3(v, actiondata->controller_loc, actiondata->controller_loc_other); + scale = len_v3(v); + mul_v3_fl(curr[0], scale); + mul_v3_fl(curr[1], scale); + mul_v3_fl(curr[2], scale); + } + + if (obedit) { + mul_m4_m4m4(prev, obedit->imat, prev); + mul_m4_m4m4(curr, obedit->imat, curr); + } + + if (nav_lock) { + wm_xr_navlocks_apply(nav_mat, nav_inv, loc_lock, locz_lock, rotz_lock, prev, curr); + } + + if (reverse) { + invert_m4(curr); + mul_m4_m4m4(r_delta, prev, curr); + } + else { + invert_m4(prev); + mul_m4_m4m4(r_delta, curr, prev); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name XR Navigation Grab + * + * Navigates the scene by grabbing with XR controllers. + * \{ */ + +static int wm_xr_navigation_grab_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!wm_xr_operator_test_event(op, event)) { + return OPERATOR_PASS_THROUGH; + } + + const wmXrActionData *actiondata = event->customdata; + + wm_xr_grab_init(op); + wm_xr_grab_update(op, actiondata); + + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; +} + +static int wm_xr_navigation_grab_exec(bContext *UNUSED(C), wmOperator *UNUSED(op)) +{ + return OPERATOR_CANCELLED; +} + +static int wm_xr_navigation_grab_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!wm_xr_operator_test_event(op, event)) { + return OPERATOR_PASS_THROUGH; + } + + const wmXrActionData *actiondata = event->customdata; + XrGrabData *data = op->customdata; + wmWindowManager *wm = CTX_wm_manager(C); + wmXrData *xr = &wm->xr; + bool loc_lock, locz_lock, rot_lock, rotz_lock, scale_lock; + GHOST_XrPose nav_pose; + float nav_scale, nav_mat[4][4], nav_inv[4][4], delta[4][4], out[4][4]; + + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "lock_location"); + loc_lock = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + prop = RNA_struct_find_property(op->ptr, "lock_location_z"); + locz_lock = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + prop = RNA_struct_find_property(op->ptr, "lock_rotation"); + rot_lock = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + prop = RNA_struct_find_property(op->ptr, "lock_rotation_z"); + rotz_lock = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + prop = RNA_struct_find_property(op->ptr, "lock_scale"); + scale_lock = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + + const bool do_bimanual = (actiondata->bimanual && data->bimanual_prev); + const bool apply_navigation = (do_bimanual ? !(loc_lock && rot_lock && scale_lock) : + !(loc_lock && rot_lock)) && + (actiondata->bimanual || !data->bimanual_prev); + + if (apply_navigation) { + const bool nav_lock = (loc_lock || locz_lock || rotz_lock); + + WM_xr_session_state_nav_location_get(xr, nav_pose.position); + WM_xr_session_state_nav_rotation_get(xr, nav_pose.orientation_quat); + WM_xr_session_state_nav_scale_get(xr, &nav_scale); + + wm_xr_pose_scale_to_mat(&nav_pose, nav_scale, nav_mat); + if (nav_lock) { + wm_xr_pose_scale_to_imat(&nav_pose, nav_scale, nav_inv); + } + + if (do_bimanual) { + wm_xr_grab_compute_bimanual(actiondata, + data, + NULL, + nav_lock ? nav_mat : NULL, + nav_lock ? nav_inv : NULL, + true, + loc_lock, + locz_lock, + rot_lock, + rotz_lock, + scale_lock, + delta); + } + else { + wm_xr_grab_compute(actiondata, + data, + NULL, + nav_lock ? nav_mat : NULL, + nav_lock ? nav_inv : NULL, + true, + loc_lock, + locz_lock, + rot_lock, + rotz_lock, + delta); + } + + mul_m4_m4m4(out, delta, nav_mat); + + /* Limit scale to reasonable values. */ + nav_scale = len_v3(out[0]); + + if (!(nav_scale < 0.001f || nav_scale > 1000.0f)) { + WM_xr_session_state_nav_location_set(xr, out[3]); + if (!rot_lock) { + mat4_to_quat(nav_pose.orientation_quat, out); + normalize_qt(nav_pose.orientation_quat); + WM_xr_session_state_nav_rotation_set(xr, nav_pose.orientation_quat); + } + if (!scale_lock && do_bimanual) { + WM_xr_session_state_nav_scale_set(xr, nav_scale); + } + } + } + + if (actiondata->bimanual) { + if (!data->bimanual_prev) { + quat_to_mat4(data->mat_prev, actiondata->controller_rot); + copy_v3_v3(data->mat_prev[3], actiondata->controller_loc); + quat_to_mat4(data->mat_other_prev, actiondata->controller_rot_other); + copy_v3_v3(data->mat_other_prev[3], actiondata->controller_loc_other); + } + data->bimanual_prev = true; + } + else { + if (data->bimanual_prev) { + quat_to_mat4(data->mat_prev, actiondata->controller_rot); + copy_v3_v3(data->mat_prev[3], actiondata->controller_loc); + } + data->bimanual_prev = false; + } + + if (event->val == KM_PRESS) { + return OPERATOR_RUNNING_MODAL; + } + else if (event->val == KM_RELEASE) { + wm_xr_grab_uninit(op); + return OPERATOR_FINISHED; + } + + /* XR events currently only support press and release. */ + BLI_assert(false); + wm_xr_grab_uninit(op); + return OPERATOR_CANCELLED; +} + +static void WM_OT_xr_navigation_grab(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "XR Navigation Grab"; + ot->idname = "WM_OT_xr_navigation_grab"; + ot->description = "Navigate the VR scene by grabbing with controllers"; + + /* callbacks */ + ot->invoke = wm_xr_navigation_grab_invoke; + ot->exec = wm_xr_navigation_grab_exec; + ot->modal = wm_xr_navigation_grab_modal; + ot->poll = wm_xr_operator_sessionactive; + + /* properties */ + RNA_def_boolean( + ot->srna, "lock_location", false, "Lock Location", "Prevent changes to viewer location"); + RNA_def_boolean( + ot->srna, "lock_location_z", false, "Lock Elevation", "Prevent changes to viewer elevation"); + RNA_def_boolean( + ot->srna, "lock_rotation", false, "Lock Rotation", "Prevent changes to viewer rotation"); + RNA_def_boolean(ot->srna, + "lock_rotation_z", + false, + "Lock Up Orientation", + "Prevent changes to viewer up orientation"); + RNA_def_boolean(ot->srna, "lock_scale", false, "Lock Scale", "Prevent changes to viewer scale"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name XR Raycast Utilities + * \{ */ + +static const float g_xr_default_raycast_axis[3] = {0.0f, 0.0f, -1.0f}; +static const float g_xr_default_raycast_color[4] = {0.35f, 0.35f, 1.0f, 1.0f}; + +typedef struct XrRaycastData { + bool from_viewer; + float origin[3]; + float direction[3]; + float end[3]; + float color[4]; + void *draw_handle; +} XrRaycastData; + +static void wm_xr_raycast_draw(const bContext *UNUSED(C), + ARegion *UNUSED(region), + void *customdata) +{ + const XrRaycastData *data = customdata; + + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + + if (data->from_viewer) { + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + immUniformColor4fv(data->color); + + GPU_depth_test(GPU_DEPTH_NONE); + GPU_point_size(7.0f); + + immBegin(GPU_PRIM_POINTS, 1); + immVertex3fv(pos, data->end); + immEnd(); + } + else { + uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_FLAT_COLOR); + + float viewport[4]; + GPU_viewport_size_get_f(viewport); + immUniform2fv("viewportSize", &viewport[2]); + + immUniform1f("lineWidth", 3.0f * U.pixelsize); + + GPU_depth_test(GPU_DEPTH_LESS_EQUAL); + + immBegin(GPU_PRIM_LINES, 2); + immAttrSkip(col); + immVertex3fv(pos, data->origin); + immAttr4fv(col, data->color); + immVertex3fv(pos, data->end); + immEnd(); + } + + immUnbindProgram(); +} + +static void wm_xr_raycast_init(wmOperator *op) +{ + BLI_assert(op->customdata == NULL); + + op->customdata = MEM_callocN(sizeof(XrRaycastData), __func__); + + SpaceType *st = BKE_spacetype_from_id(SPACE_VIEW3D); + if (st) { + ARegionType *art = BKE_regiontype_from_id(st, RGN_TYPE_XR); + if (art) { + ((XrRaycastData *)op->customdata)->draw_handle = ED_region_draw_cb_activate( + art, wm_xr_raycast_draw, op->customdata, REGION_DRAW_POST_VIEW); + } + } +} + +static void wm_xr_raycast_uninit(wmOperator *op) +{ + if (op->customdata) { + SpaceType *st = BKE_spacetype_from_id(SPACE_VIEW3D); + if (st) { + ARegionType *art = BKE_regiontype_from_id(st, RGN_TYPE_XR); + if (art) { + ED_region_draw_cb_exit(art, ((XrRaycastData *)op->customdata)->draw_handle); + } + } + + MEM_freeN(op->customdata); + } +} + +static void wm_xr_raycast_update(wmOperator *op, + const wmXrData *xr, + const wmXrActionData *actiondata) +{ + XrRaycastData *data = op->customdata; + float ray_length, axis[3]; + + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "from_viewer"); + data->from_viewer = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + + prop = RNA_struct_find_property(op->ptr, "axis"); + if (prop) { + RNA_property_float_get_array(op->ptr, prop, axis); + normalize_v3(axis); + } + else { + copy_v3_v3(axis, g_xr_default_raycast_axis); + } + + prop = RNA_struct_find_property(op->ptr, "color"); + if (prop) { + RNA_property_float_get_array(op->ptr, prop, data->color); + } + else { + copy_v4_v4(data->color, g_xr_default_raycast_color); + } + + if (data->from_viewer) { + float viewer_rot[4]; + WM_xr_session_state_viewer_pose_location_get(xr, data->origin); + WM_xr_session_state_viewer_pose_rotation_get(xr, viewer_rot); + mul_qt_v3(viewer_rot, axis); + ray_length = (xr->session_settings.clip_start + xr->session_settings.clip_end) / 2.0f; + } + else { + copy_v3_v3(data->origin, actiondata->controller_loc); + mul_qt_v3(actiondata->controller_rot, axis); + ray_length = xr->session_settings.clip_end; + } + + copy_v3_v3(data->direction, axis); + madd_v3_v3v3fl(data->end, data->origin, data->direction, ray_length); +} + +static void wm_xr_raycast(Depsgraph *depsgraph, + ViewContext *vc, + const float origin[3], + const float direction[3], + float *ray_dist, + bool selectable_only, + float r_location[3], + float r_normal[3], + int *r_index, + Object **r_ob, + float r_obmat[4][4]) +{ + /* Uses same raycast method as Scene.ray_cast(). */ + SnapObjectContext *sctx = ED_transform_snap_object_context_create(vc->scene, 0); + + ED_transform_snap_object_project_ray_ex( + sctx, + depsgraph, + &(const struct SnapObjectParams){ + .snap_select = vc->em ? SNAP_SELECTED : (selectable_only ? SNAP_SELECTABLE : SNAP_ALL)}, + origin, + direction, + ray_dist, + r_location, + r_normal, + r_index, + r_ob, + r_obmat); + + ED_transform_snap_object_context_destroy(sctx); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name XR Navigation Fly + * + * Navigates the scene by moving/turning relative to navigation space or the XR viewer or + * controller. + * \{ */ + +#define XR_DEFAULT_FLY_SPEED_MOVE 0.054f +#define XR_DEFAULT_FLY_SPEED_TURN 0.03f + +typedef enum eXrFlyMode { + XR_FLY_FORWARD = 0, + XR_FLY_BACK = 1, + XR_FLY_LEFT = 2, + XR_FLY_RIGHT = 3, + XR_FLY_UP = 4, + XR_FLY_DOWN = 5, + XR_FLY_TURNLEFT = 6, + XR_FLY_TURNRIGHT = 7, + XR_FLY_VIEWER_FORWARD = 8, + XR_FLY_VIEWER_BACK = 9, + XR_FLY_VIEWER_LEFT = 10, + XR_FLY_VIEWER_RIGHT = 11, + XR_FLY_CONTROLLER_FORWARD = 12, +} eXrFlyMode; + +typedef struct XrFlyData { + float viewer_rot[4]; + double time_prev; +} XrFlyData; + +static void wm_xr_fly_init(wmOperator *op, const wmXrData *xr) +{ + BLI_assert(op->customdata == NULL); + + XrFlyData *data = op->customdata = MEM_callocN(sizeof(XrFlyData), __func__); + + WM_xr_session_state_viewer_pose_rotation_get(xr, data->viewer_rot); + data->time_prev = PIL_check_seconds_timer(); +} + +static void wm_xr_fly_uninit(wmOperator *op) +{ + MEM_SAFE_FREE(op->customdata); +} + +static void wm_xr_fly_compute_move(eXrFlyMode mode, + float speed, + const float ref_quat[4], + const float nav_mat[4][4], + bool locz_lock, + float r_delta[4][4]) +{ + float ref_axes[3][3]; + quat_to_mat3(ref_axes, ref_quat); + + unit_m4(r_delta); + + switch (mode) { + /* Navigation space reference. */ + case XR_FLY_FORWARD: + madd_v3_v3fl(r_delta[3], ref_axes[1], speed); + return; + case XR_FLY_BACK: + madd_v3_v3fl(r_delta[3], ref_axes[1], -speed); + return; + case XR_FLY_LEFT: + madd_v3_v3fl(r_delta[3], ref_axes[0], -speed); + return; + case XR_FLY_RIGHT: + madd_v3_v3fl(r_delta[3], ref_axes[0], speed); + return; + case XR_FLY_UP: + case XR_FLY_DOWN: + if (!locz_lock) { + madd_v3_v3fl(r_delta[3], ref_axes[2], (mode == XR_FLY_UP) ? speed : -speed); + } + return; + /* Viewer/controller space reference. */ + case XR_FLY_VIEWER_FORWARD: + case XR_FLY_CONTROLLER_FORWARD: + negate_v3_v3(r_delta[3], ref_axes[2]); + break; + case XR_FLY_VIEWER_BACK: + copy_v3_v3(r_delta[3], ref_axes[2]); + break; + case XR_FLY_VIEWER_LEFT: + negate_v3_v3(r_delta[3], ref_axes[0]); + break; + case XR_FLY_VIEWER_RIGHT: + copy_v3_v3(r_delta[3], ref_axes[0]); + break; + /* Unused. */ + case XR_FLY_TURNLEFT: + case XR_FLY_TURNRIGHT: + BLI_assert_unreachable(); + return; + } + + if (locz_lock) { + /* Lock elevation in navigation space. */ + float z_axis[3], projected[3]; + + normalize_v3_v3(z_axis, nav_mat[2]); + project_v3_v3v3_normalized(projected, r_delta[3], z_axis); + sub_v3_v3(r_delta[3], projected); + + normalize_v3(r_delta[3]); + } + + mul_v3_fl(r_delta[3], speed); +} + +static void wm_xr_fly_compute_turn(eXrFlyMode mode, + float speed, + const float viewer_mat[4][4], + const float nav_mat[4][4], + const float nav_inv[4][4], + float r_delta[4][4]) +{ + BLI_assert(mode == XR_FLY_TURNLEFT || mode == XR_FLY_TURNRIGHT); + + float z_axis[3], m[3][3], prev[4][4], curr[4][4]; + + /* Turn around Z-axis in navigation space. */ + normalize_v3_v3(z_axis, nav_mat[2]); + axis_angle_normalized_to_mat3(m, z_axis, (mode == XR_FLY_TURNLEFT) ? speed : -speed); + copy_m4_m3(r_delta, m); + + copy_m4_m4(prev, viewer_mat); + mul_m4_m4m4(curr, r_delta, viewer_mat); + + /* Lock location in base pose space. */ + wm_xr_navlocks_apply(nav_mat, nav_inv, true, false, false, prev, curr); + + invert_m4(prev); + mul_m4_m4m4(r_delta, curr, prev); +} + +static void wm_xr_basenav_rotation_calc(const wmXrData *xr, + const float nav_rotation[4], + float r_rotation[4]) +{ + /* Apply nav rotation to base pose Z-rotation. */ + float base_eul[3], base_quatz[4]; + quat_to_eul(base_eul, xr->runtime->session_state.prev_base_pose.orientation_quat); + axis_angle_to_quat_single(base_quatz, 'Z', base_eul[2]); + mul_qt_qtqt(r_rotation, nav_rotation, base_quatz); +} + +static int wm_xr_navigation_fly_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!wm_xr_operator_test_event(op, event)) { + return OPERATOR_PASS_THROUGH; + } + + wmWindowManager *wm = CTX_wm_manager(C); + + wm_xr_fly_init(op, &wm->xr); + + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; +} + +static int wm_xr_navigation_fly_exec(bContext *UNUSED(C), wmOperator *UNUSED(op)) +{ + return OPERATOR_CANCELLED; +} + +static int wm_xr_navigation_fly_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!wm_xr_operator_test_event(op, event)) { + return OPERATOR_PASS_THROUGH; + } + + if (event->val == KM_RELEASE) { + wm_xr_fly_uninit(op); + return OPERATOR_FINISHED; + } + + const wmXrActionData *actiondata = event->customdata; + XrFlyData *data = op->customdata; + wmWindowManager *wm = CTX_wm_manager(C); + wmXrData *xr = &wm->xr; + eXrFlyMode mode; + bool turn, locz_lock, dir_lock, speed_frame_based; + bool speed_interp_cubic = false; + float speed, speed_max, speed_p0[2], speed_p1[2]; + GHOST_XrPose nav_pose; + float nav_mat[4][4], delta[4][4], out[4][4]; + + const double time_now = PIL_check_seconds_timer(); + + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "mode"); + mode = prop ? RNA_property_enum_get(op->ptr, prop) : XR_FLY_VIEWER_FORWARD; + + turn = (mode == XR_FLY_TURNLEFT || mode == XR_FLY_TURNRIGHT); + + prop = RNA_struct_find_property(op->ptr, "lock_location_z"); + locz_lock = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + + prop = RNA_struct_find_property(op->ptr, "lock_direction"); + dir_lock = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + + prop = RNA_struct_find_property(op->ptr, "speed_frame_based"); + speed_frame_based = prop ? RNA_property_boolean_get(op->ptr, prop) : true; + + prop = RNA_struct_find_property(op->ptr, "speed_min"); + speed = prop ? RNA_property_float_get(op->ptr, prop) : + (turn ? XR_DEFAULT_FLY_SPEED_TURN : XR_DEFAULT_FLY_SPEED_MOVE) / + (speed_frame_based ? 3.0f : 0.03f); + + prop = RNA_struct_find_property(op->ptr, "speed_max"); + speed_max = prop ? RNA_property_float_get(op->ptr, prop) : + (turn ? XR_DEFAULT_FLY_SPEED_TURN : XR_DEFAULT_FLY_SPEED_MOVE) / + (speed_frame_based ? 1.0f : 0.01f); + + prop = RNA_struct_find_property(op->ptr, "speed_interpolation0"); + if (prop && RNA_property_is_set(op->ptr, prop)) { + RNA_property_float_get_array(op->ptr, prop, speed_p0); + speed_interp_cubic = true; + } + else { + speed_p0[0] = speed_p0[1] = 0.0f; + } + + prop = RNA_struct_find_property(op->ptr, "speed_interpolation1"); + if (prop && RNA_property_is_set(op->ptr, prop)) { + RNA_property_float_get_array(op->ptr, prop, speed_p1); + speed_interp_cubic = true; + } + else { + speed_p1[0] = speed_p1[1] = 1.0f; + } + + /* Ensure valid interpolation. */ + if (speed_max < speed) { + speed_max = speed; + } + + /* Interpolate between min/max speeds based on button state. */ + switch (actiondata->type) { + case XR_BOOLEAN_INPUT: + speed = speed_max; + break; + case XR_FLOAT_INPUT: + case XR_VECTOR2F_INPUT: { + float state = (actiondata->type == XR_FLOAT_INPUT) ? fabsf(actiondata->state[0]) : + len_v2(actiondata->state); + float speed_t = (actiondata->float_threshold < 1.0f) ? + (state - actiondata->float_threshold) / + (1.0f - actiondata->float_threshold) : + 1.0f; + if (speed_interp_cubic) { + float start[2], end[2], p[2]; + + start[0] = 0.0f; + start[1] = speed; + speed_p0[1] = speed + speed_p0[1] * (speed_max - speed); + speed_p1[1] = speed + speed_p1[1] * (speed_max - speed); + end[0] = 1.0f; + end[1] = speed_max; + + interp_v2_v2v2v2v2_cubic(p, start, speed_p0, speed_p1, end, speed_t); + speed = p[1]; + } + else { + speed += speed_t * (speed_max - speed); + } + break; + } + case XR_POSE_INPUT: + case XR_VIBRATION_OUTPUT: + BLI_assert_unreachable(); + break; + } + + if (!speed_frame_based) { + /* Adjust speed based on last update time. */ + speed *= time_now - data->time_prev; + } + data->time_prev = time_now; + + WM_xr_session_state_nav_location_get(xr, nav_pose.position); + WM_xr_session_state_nav_rotation_get(xr, nav_pose.orientation_quat); + wm_xr_pose_to_mat(&nav_pose, nav_mat); + + if (turn) { + if (dir_lock) { + unit_m4(delta); + } + else { + GHOST_XrPose viewer_pose; + float viewer_mat[4][4], nav_inv[4][4]; + + WM_xr_session_state_viewer_pose_location_get(xr, viewer_pose.position); + WM_xr_session_state_viewer_pose_rotation_get(xr, viewer_pose.orientation_quat); + wm_xr_pose_to_mat(&viewer_pose, viewer_mat); + wm_xr_pose_to_imat(&nav_pose, nav_inv); + + wm_xr_fly_compute_turn(mode, speed, viewer_mat, nav_mat, nav_inv, delta); + } + } + else { + float nav_scale, ref_quat[4]; + + /* Adjust speed for navigation scale. */ + WM_xr_session_state_nav_scale_get(xr, &nav_scale); + speed *= nav_scale; + + switch (mode) { + /* Move relative to navigation space. */ + case XR_FLY_FORWARD: + case XR_FLY_BACK: + case XR_FLY_LEFT: + case XR_FLY_RIGHT: + case XR_FLY_UP: + case XR_FLY_DOWN: + wm_xr_basenav_rotation_calc(xr, nav_pose.orientation_quat, ref_quat); + break; + /* Move relative to viewer. */ + case XR_FLY_VIEWER_FORWARD: + case XR_FLY_VIEWER_BACK: + case XR_FLY_VIEWER_LEFT: + case XR_FLY_VIEWER_RIGHT: + if (dir_lock) { + copy_qt_qt(ref_quat, data->viewer_rot); + } + else { + WM_xr_session_state_viewer_pose_rotation_get(xr, ref_quat); + } + break; + /* Move relative to controller. */ + case XR_FLY_CONTROLLER_FORWARD: + copy_qt_qt(ref_quat, actiondata->controller_rot); + break; + /* Unused. */ + case XR_FLY_TURNLEFT: + case XR_FLY_TURNRIGHT: + BLI_assert_unreachable(); + break; + } + + wm_xr_fly_compute_move(mode, speed, ref_quat, nav_mat, locz_lock, delta); + } + + mul_m4_m4m4(out, delta, nav_mat); + + WM_xr_session_state_nav_location_set(xr, out[3]); + if (turn) { + mat4_to_quat(nav_pose.orientation_quat, out); + WM_xr_session_state_nav_rotation_set(xr, nav_pose.orientation_quat); + } + + if (event->val == KM_PRESS) { + return OPERATOR_RUNNING_MODAL; + } + + /* XR events currently only support press and release. */ + BLI_assert(false); + wm_xr_fly_uninit(op); + return OPERATOR_CANCELLED; +} + +static void WM_OT_xr_navigation_fly(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "XR Navigation Fly"; + ot->idname = "WM_OT_xr_navigation_fly"; + ot->description = "Move/turn relative to the VR viewer or controller"; + + /* callbacks */ + ot->invoke = wm_xr_navigation_fly_invoke; + ot->exec = wm_xr_navigation_fly_exec; + ot->modal = wm_xr_navigation_fly_modal; + ot->poll = wm_xr_operator_sessionactive; + + /* properties */ + static const EnumPropertyItem fly_modes[] = { + {XR_FLY_FORWARD, "FORWARD", 0, "Forward", "Move along navigation forward axis"}, + {XR_FLY_BACK, "BACK", 0, "Back", "Move along navigation back axis"}, + {XR_FLY_LEFT, "LEFT", 0, "Left", "Move along navigation left axis"}, + {XR_FLY_RIGHT, "RIGHT", 0, "Right", "Move along navigation right axis"}, + {XR_FLY_UP, "UP", 0, "Up", "Move along navigation up axis"}, + {XR_FLY_DOWN, "DOWN", 0, "Down", "Move along navigation down axis"}, + {XR_FLY_TURNLEFT, + "TURNLEFT", + 0, + "Turn Left", + "Turn counter-clockwise around navigation up axis"}, + {XR_FLY_TURNRIGHT, "TURNRIGHT", 0, "Turn Right", "Turn clockwise around navigation up axis"}, + {XR_FLY_VIEWER_FORWARD, + "VIEWER_FORWARD", + 0, + "Viewer Forward", + "Move along viewer's forward axis"}, + {XR_FLY_VIEWER_BACK, "VIEWER_BACK", 0, "Viewer Back", "Move along viewer's back axis"}, + {XR_FLY_VIEWER_LEFT, "VIEWER_LEFT", 0, "Viewer Left", "Move along viewer's left axis"}, + {XR_FLY_VIEWER_RIGHT, "VIEWER_RIGHT", 0, "Viewer Right", "Move along viewer's right axis"}, + {XR_FLY_CONTROLLER_FORWARD, + "CONTROLLER_FORWARD", + 0, + "Controller Forward", + "Move along controller's forward axis"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const float default_speed_p0[2] = {0.0f, 0.0f}; + static const float default_speed_p1[2] = {1.0f, 1.0f}; + + RNA_def_enum(ot->srna, "mode", fly_modes, XR_FLY_VIEWER_FORWARD, "Mode", "Fly mode"); + RNA_def_boolean( + ot->srna, "lock_location_z", false, "Lock Elevation", "Prevent changes to viewer elevation"); + RNA_def_boolean(ot->srna, + "lock_direction", + false, + "Lock Direction", + "Limit movement to viewer's intial direction"); + RNA_def_boolean(ot->srna, + "speed_frame_based", + true, + "Frame Based Speed", + "Apply fixed movement deltas every update"); + RNA_def_float(ot->srna, + "speed_min", + XR_DEFAULT_FLY_SPEED_MOVE / 3.0f, + 0.0f, + 1000.0f, + "Minimum Speed", + "Minimum move (turn) speed in meters (radians) per second or frame", + 0.0f, + 1000.0f); + RNA_def_float(ot->srna, + "speed_max", + XR_DEFAULT_FLY_SPEED_MOVE, + 0.0f, + 1000.0f, + "Maximum Speed", + "Maximum move (turn) speed in meters (radians) per second or frame", + 0.0f, + 1000.0f); + RNA_def_float_vector(ot->srna, + "speed_interpolation0", + 2, + default_speed_p0, + 0.0f, + 1.0f, + "Speed Interpolation 0", + "First cubic spline control point between min/max speeds", + 0.0f, + 1.0f); + RNA_def_float_vector(ot->srna, + "speed_interpolation1", + 2, + default_speed_p1, + 0.0f, + 1.0f, + "Speed Interpolation 1", + "Second cubic spline control point between min/max speeds", + 0.0f, + 1.0f); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name XR Navigation Teleport + * + * Casts a ray from an XR controller's pose and teleports to any hit geometry. + * \{ */ + +static void wm_xr_navigation_teleport(bContext *C, + wmXrData *xr, + const float origin[3], + const float direction[3], + float *ray_dist, + bool selectable_only, + const bool teleport_axes[3], + float teleport_t, + float teleport_ofs) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); + vc.em = NULL; /* Set to NULL to always enable teleport to non-edited objects. */ + + float location[3]; + float normal[3]; + int index; + Object *ob = NULL; + float obmat[4][4]; + + wm_xr_raycast(depsgraph, + &vc, + origin, + direction, + ray_dist, + selectable_only, + location, + normal, + &index, + &ob, + obmat); + + /* Teleport. */ + if (ob) { + float nav_location[3], nav_rotation[4], viewer_location[3]; + float nav_axes[3][3], projected[3], v0[3], v1[3]; + float out[3] = {0.0f, 0.0f, 0.0f}; + + WM_xr_session_state_nav_location_get(xr, nav_location); + WM_xr_session_state_nav_rotation_get(xr, nav_rotation); + WM_xr_session_state_viewer_pose_location_get(xr, viewer_location); + + wm_xr_basenav_rotation_calc(xr, nav_rotation, nav_rotation); + quat_to_mat3(nav_axes, nav_rotation); + + /* Project locations onto navigation axes. */ + for (int a = 0; a < 3; ++a) { + project_v3_v3v3_normalized(projected, nav_location, nav_axes[a]); + if (teleport_axes[a]) { + /* Interpolate between projected locations. */ + project_v3_v3v3_normalized(v0, location, nav_axes[a]); + project_v3_v3v3_normalized(v1, viewer_location, nav_axes[a]); + sub_v3_v3(v0, v1); + madd_v3_v3fl(projected, v0, teleport_t); + /* Subtract offset. */ + project_v3_v3v3_normalized(v0, normal, nav_axes[a]); + madd_v3_v3fl(projected, v0, teleport_ofs); + } + /* Add to final location. */ + add_v3_v3(out, projected); + } + + WM_xr_session_state_nav_location_set(xr, out); + } +} + +static int wm_xr_navigation_teleport_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!wm_xr_operator_test_event(op, event)) { + return OPERATOR_PASS_THROUGH; + } + + wm_xr_raycast_init(op); + + int retval = op->type->modal(C, op, event); + + if ((retval & OPERATOR_RUNNING_MODAL) != 0) { + WM_event_add_modal_handler(C, op); + } + + return retval; +} + +static int wm_xr_navigation_teleport_exec(bContext *UNUSED(C), wmOperator *UNUSED(op)) +{ + return OPERATOR_CANCELLED; +} + +static int wm_xr_navigation_teleport_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!wm_xr_operator_test_event(op, event)) { + return OPERATOR_PASS_THROUGH; + } + + const wmXrActionData *actiondata = event->customdata; + wmWindowManager *wm = CTX_wm_manager(C); + wmXrData *xr = &wm->xr; + + wm_xr_raycast_update(op, xr, actiondata); + + if (event->val == KM_PRESS) { + return OPERATOR_RUNNING_MODAL; + } + else if (event->val == KM_RELEASE) { + XrRaycastData *data = op->customdata; + bool selectable_only, teleport_axes[3]; + float teleport_t, teleport_ofs, ray_dist; + + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "teleport_axes"); + if (prop) { + RNA_property_boolean_get_array(op->ptr, prop, teleport_axes); + } + else { + teleport_axes[0] = teleport_axes[1] = teleport_axes[2] = true; + } + + prop = RNA_struct_find_property(op->ptr, "interpolation"); + teleport_t = prop ? RNA_property_float_get(op->ptr, prop) : 1.0f; + + prop = RNA_struct_find_property(op->ptr, "offset"); + teleport_ofs = prop ? RNA_property_float_get(op->ptr, prop) : 0.0f; + + prop = RNA_struct_find_property(op->ptr, "selectable_only"); + selectable_only = prop ? RNA_property_boolean_get(op->ptr, prop) : true; + + prop = RNA_struct_find_property(op->ptr, "distance"); + ray_dist = prop ? RNA_property_float_get(op->ptr, prop) : BVH_RAYCAST_DIST_MAX; + + wm_xr_navigation_teleport(C, + xr, + data->origin, + data->direction, + &ray_dist, + selectable_only, + teleport_axes, + teleport_t, + teleport_ofs); + + wm_xr_raycast_uninit(op); + + return OPERATOR_FINISHED; + } + + /* XR events currently only support press and release. */ + BLI_assert(false); + wm_xr_raycast_uninit(op); + return OPERATOR_CANCELLED; +} + +static void WM_OT_xr_navigation_teleport(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "XR Navigation Teleport"; + ot->idname = "WM_OT_xr_navigation_teleport"; + ot->description = "Set VR viewer location to controller raycast hit location"; + + /* callbacks */ + ot->invoke = wm_xr_navigation_teleport_invoke; + ot->exec = wm_xr_navigation_teleport_exec; + ot->modal = wm_xr_navigation_teleport_modal; + ot->poll = wm_xr_operator_sessionactive; + + /* properties */ + static bool default_teleport_axes[3] = {true, true, true}; + + RNA_def_boolean_vector(ot->srna, + "teleport_axes", + 3, + default_teleport_axes, + "Teleport Axes", + "Enabled teleport axes in navigation space"); + RNA_def_float(ot->srna, + "interpolation", + 1.0f, + 0.0f, + 1.0f, + "Interpolation", + "Interpolation factor between viewer and hit locations", + 0.0f, + 1.0f); + RNA_def_float(ot->srna, + "offset", + 0.0f, + 0.0f, + FLT_MAX, + "Offset", + "Offset along hit normal to subtract from final location", + 0.0f, + FLT_MAX); + RNA_def_boolean(ot->srna, + "selectable_only", + true, + "Selectable Only", + "Only allow selectable objects to influence raycast result"); + RNA_def_float(ot->srna, + "distance", + BVH_RAYCAST_DIST_MAX, + 0.0, + BVH_RAYCAST_DIST_MAX, + "", + "Maximum raycast distance", + 0.0, + BVH_RAYCAST_DIST_MAX); + RNA_def_boolean( + ot->srna, "from_viewer", false, "From Viewer", "Use viewer pose as raycast origin"); + RNA_def_float_vector(ot->srna, + "axis", + 3, + g_xr_default_raycast_axis, + -1.0f, + 1.0f, + "Axis", + "Raycast axis in controller/viewer space", + -1.0f, + 1.0f); + RNA_def_float_color(ot->srna, + "color", + 4, + g_xr_default_raycast_color, + 0.0f, + 1.0f, + "Color", + "Raycast color", + 0.0f, + 1.0f); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name XR Navigation Reset + * + * Resets XR navigation deltas relative to session base pose. + * \{ */ + +static int wm_xr_navigation_reset_exec(bContext *C, wmOperator *op) +{ + wmWindowManager *wm = CTX_wm_manager(C); + wmXrData *xr = &wm->xr; + bool reset_loc, reset_rot, reset_scale; + + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "location"); + reset_loc = prop ? RNA_property_boolean_get(op->ptr, prop) : true; + + prop = RNA_struct_find_property(op->ptr, "rotation"); + reset_rot = prop ? RNA_property_boolean_get(op->ptr, prop) : true; + + prop = RNA_struct_find_property(op->ptr, "scale"); + reset_scale = prop ? RNA_property_boolean_get(op->ptr, prop) : true; + + if (reset_loc) { + float loc[3]; + if (!reset_scale) { + float nav_rotation[4], nav_scale; + + WM_xr_session_state_nav_rotation_get(xr, nav_rotation); + WM_xr_session_state_nav_scale_get(xr, &nav_scale); + + /* Adjust location based on scale. */ + mul_v3_v3fl(loc, xr->runtime->session_state.prev_base_pose.position, nav_scale); + sub_v3_v3(loc, xr->runtime->session_state.prev_base_pose.position); + mul_qt_v3(nav_rotation, loc); + negate_v3(loc); + } + else { + zero_v3(loc); + } + WM_xr_session_state_nav_location_set(xr, loc); + } + + if (reset_rot) { + float rot[4]; + unit_qt(rot); + WM_xr_session_state_nav_rotation_set(xr, rot); + } + + if (reset_scale) { + if (!reset_loc) { + float nav_location[3], nav_rotation[4], nav_scale; + float nav_axes[3][3], v[3]; + + WM_xr_session_state_nav_location_get(xr, nav_location); + WM_xr_session_state_nav_rotation_get(xr, nav_rotation); + WM_xr_session_state_nav_scale_get(xr, &nav_scale); + + /* Offset any location changes when changing scale. */ + mul_v3_v3fl(v, xr->runtime->session_state.prev_base_pose.position, nav_scale); + sub_v3_v3(v, xr->runtime->session_state.prev_base_pose.position); + mul_qt_v3(nav_rotation, v); + add_v3_v3(nav_location, v); + + /* Reset elevation to base pose value. */ + quat_to_mat3(nav_axes, nav_rotation); + project_v3_v3v3_normalized(v, nav_location, nav_axes[2]); + sub_v3_v3(nav_location, v); + + WM_xr_session_state_nav_location_set(xr, nav_location); + } + WM_xr_session_state_nav_scale_set(xr, 1.0f); + } + + return OPERATOR_FINISHED; +} + +static void WM_OT_xr_navigation_reset(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "XR Navigation Reset"; + ot->idname = "WM_OT_xr_navigation_reset"; + ot->description = "Reset VR navigation deltas relative to session base pose"; + + /* callbacks */ + ot->exec = wm_xr_navigation_reset_exec; + ot->poll = wm_xr_operator_sessionactive; + + /* properties */ + RNA_def_boolean(ot->srna, "location", true, "Location", "Reset location deltas"); + RNA_def_boolean(ot->srna, "rotation", true, "Rotation", "Reset rotation deltas"); + RNA_def_boolean(ot->srna, "scale", true, "Scale", "Reset scale deltas"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name XR Raycast Select + * + * Casts a ray from an XR controller's pose and selects any hit geometry. + * \{ */ + +typedef enum eXrSelectElem { + XR_SEL_BASE = 0, + XR_SEL_VERTEX = 1, + XR_SEL_EDGE = 2, + XR_SEL_FACE = 3, +} eXrSelectElem; + +static void wm_xr_select_op_apply(void *elem, + BMesh *bm, + eXrSelectElem select_elem, + eSelectOp select_op, + bool *r_changed, + bool *r_set) +{ + const bool selected_prev = (select_elem == XR_SEL_BASE) ? + (((Base *)elem)->flag & BASE_SELECTED) != 0 : + (((BMElem *)elem)->head.hflag & BM_ELEM_SELECT) != 0; + + if (selected_prev) { + switch (select_op) { + case SEL_OP_SUB: + case SEL_OP_XOR: { + switch (select_elem) { + case XR_SEL_BASE: + ED_object_base_select((Base *)elem, BA_DESELECT); + *r_changed = true; + break; + case XR_SEL_VERTEX: + BM_vert_select_set(bm, (BMVert *)elem, false); + *r_changed = true; + break; + case XR_SEL_EDGE: + BM_edge_select_set(bm, (BMEdge *)elem, false); + *r_changed = true; + break; + case XR_SEL_FACE: + BM_face_select_set(bm, (BMFace *)elem, false); + *r_changed = true; + break; + } + break; + } + default: { + break; + } + } + } + else { + switch (select_op) { + case SEL_OP_SET: + case SEL_OP_ADD: + case SEL_OP_XOR: { + switch (select_elem) { + case XR_SEL_BASE: + ED_object_base_select((Base *)elem, BA_SELECT); + *r_changed = true; + break; + case XR_SEL_VERTEX: + BM_vert_select_set(bm, (BMVert *)elem, true); + *r_changed = true; + break; + case XR_SEL_EDGE: + BM_edge_select_set(bm, (BMEdge *)elem, true); + *r_changed = true; + break; + case XR_SEL_FACE: + BM_face_select_set(bm, (BMFace *)elem, true); + *r_changed = true; + break; + } + } + default: { + break; + } + } + + if (select_op == SEL_OP_SET) { + *r_set = true; + } + } +} + +static bool wm_xr_select_raycast(bContext *C, + const float origin[3], + const float direction[3], + float *ray_dist, + bool selectable_only, + eSelectOp select_op, + bool deselect_all) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); + vc.em = (vc.obedit && (vc.obedit->type == OB_MESH)) ? BKE_editmesh_from_object(vc.obedit) : NULL; + + float location[3]; + float normal[3]; + int index; + Object *ob = NULL; + float obmat[4][4]; + + wm_xr_raycast(depsgraph, + &vc, + origin, + direction, + ray_dist, + selectable_only, + location, + normal, + &index, + &ob, + obmat); + + /* Select. */ + bool hit = false; + bool changed = false; + + if (ob && vc.em && + ((ob == vc.obedit) || (ob->id.orig_id == &vc.obedit->id))) { /* TODO_XR: Non-mesh objects. */ + BMesh *bm = vc.em->bm; + BMFace *f = NULL; + BMEdge *e = NULL; + BMVert *v = NULL; + + if (index != -1) { + ToolSettings *ts = vc.scene->toolsettings; + float co[3]; + f = BM_face_at_index(bm, index); + + if ((ts->selectmode & SCE_SELECT_VERTEX) != 0) { + /* Find nearest vertex. */ + float dist_max = *ray_dist; + float dist; + BMLoop *l = f->l_first; + for (int i = 0; i < f->len; ++i, l = l->next) { + mul_v3_m4v3(co, obmat, l->v->co); + if ((dist = len_manhattan_v3v3(location, co)) < dist_max) { + v = l->v; + dist_max = dist; + } + } + if (v) { + hit = true; + } + } + if ((ts->selectmode & SCE_SELECT_EDGE) != 0) { + /* Find nearest edge. */ + float dist_max = *ray_dist; + float dist; + BMLoop *l = f->l_first; + for (int i = 0; i < f->len; ++i, l = l->next) { + add_v3_v3v3(co, l->e->v1->co, l->e->v2->co); + mul_v3_fl(co, 0.5f); + mul_m4_v3(obmat, co); + if ((dist = len_manhattan_v3v3(location, co)) < dist_max) { + e = l->e; + dist_max = dist; + } + } + if (e) { + hit = true; + } + } + if ((ts->selectmode & SCE_SELECT_FACE) != 0) { + hit = true; + } + else { + f = NULL; + } + } + + if (!hit) { + if (deselect_all) { + changed = EDBM_mesh_deselect_all_multi(C); + } + } + else { + bool set_v = false; + bool set_e = false; + bool set_f = false; + + if (v) { + wm_xr_select_op_apply(v, bm, XR_SEL_VERTEX, select_op, &changed, &set_v); + } + if (e) { + wm_xr_select_op_apply(e, bm, XR_SEL_EDGE, select_op, &changed, &set_e); + } + if (f) { + wm_xr_select_op_apply(f, bm, XR_SEL_FACE, select_op, &changed, &set_f); + } + + if (set_v || set_e || set_f) { + EDBM_mesh_deselect_all_multi(C); + if (set_v) { + BM_vert_select_set(bm, v, true); + } + if (set_e) { + BM_edge_select_set(bm, e, true); + } + if (set_f) { + BM_face_select_set(bm, f, true); + } + } + } + + if (changed) { + DEG_id_tag_update((ID *)vc.obedit->data, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); + } + } + else if (vc.em) { + if (deselect_all) { + changed = EDBM_mesh_deselect_all_multi(C); + } + + if (changed) { + DEG_id_tag_update((ID *)vc.obedit->data, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); + } + } + else { + if (ob) { + hit = true; + } + + if (!hit) { + if (deselect_all) { + changed = object_deselect_all_except(vc.view_layer, NULL); + } + } + else { + Base *base = BKE_view_layer_base_find(vc.view_layer, DEG_get_original_object(ob)); + if (base && BASE_SELECTABLE(vc.v3d, base)) { + bool set = false; + wm_xr_select_op_apply(base, NULL, XR_SEL_BASE, select_op, &changed, &set); + if (set) { + object_deselect_all_except(vc.view_layer, base); + } + } + } + + if (changed) { + DEG_id_tag_update(&vc.scene->id, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, vc.scene); + } + } + + return changed; +} + +static int wm_xr_select_raycast_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!wm_xr_operator_test_event(op, event)) { + return OPERATOR_PASS_THROUGH; + } + + wm_xr_raycast_init(op); + + int retval = op->type->modal(C, op, event); + + if ((retval & OPERATOR_RUNNING_MODAL) != 0) { + WM_event_add_modal_handler(C, op); + } + + return retval; +} + +static int wm_xr_select_raycast_exec(bContext *UNUSED(C), wmOperator *UNUSED(op)) +{ + return OPERATOR_CANCELLED; +} + +static int wm_xr_select_raycast_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!wm_xr_operator_test_event(op, event)) { + return OPERATOR_PASS_THROUGH; + } + + const wmXrActionData *actiondata = event->customdata; + wmWindowManager *wm = CTX_wm_manager(C); + wmXrData *xr = &wm->xr; + + wm_xr_raycast_update(op, xr, actiondata); + + if (event->val == KM_PRESS) { + return OPERATOR_RUNNING_MODAL; + } + else if (event->val == KM_RELEASE) { + XrRaycastData *data = op->customdata; + eSelectOp select_op = SEL_OP_SET; + bool deselect_all, selectable_only; + float ray_dist; + + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "toggle"); + if (prop && RNA_property_boolean_get(op->ptr, prop)) { + select_op = SEL_OP_XOR; + } + prop = RNA_struct_find_property(op->ptr, "deselect"); + if (prop && RNA_property_boolean_get(op->ptr, prop)) { + select_op = SEL_OP_SUB; + } + prop = RNA_struct_find_property(op->ptr, "extend"); + if (prop && RNA_property_boolean_get(op->ptr, prop)) { + select_op = SEL_OP_ADD; + } + + prop = RNA_struct_find_property(op->ptr, "deselect_all"); + deselect_all = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + + prop = RNA_struct_find_property(op->ptr, "selectable_only"); + selectable_only = prop ? RNA_property_boolean_get(op->ptr, prop) : true; + + prop = RNA_struct_find_property(op->ptr, "distance"); + ray_dist = prop ? RNA_property_float_get(op->ptr, prop) : BVH_RAYCAST_DIST_MAX; + + bool changed = wm_xr_select_raycast( + C, data->origin, data->direction, &ray_dist, selectable_only, select_op, deselect_all); + + wm_xr_raycast_uninit(op); + + return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED; + } + + /* XR events currently only support press and release. */ + BLI_assert(false); + wm_xr_raycast_uninit(op); + return OPERATOR_CANCELLED; +} + +static void WM_OT_xr_select_raycast(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "XR Raycast Select"; + ot->idname = "WM_OT_xr_select_raycast"; + ot->description = "Raycast select with a VR controller"; + + /* callbacks */ + ot->invoke = wm_xr_select_raycast_invoke; + ot->exec = wm_xr_select_raycast_exec; + ot->modal = wm_xr_select_raycast_modal; + ot->poll = wm_xr_operator_sessionactive; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* properties */ + WM_operator_properties_mouse_select(ot); + + /* Override "deselect_all" default value. */ + PropertyRNA *prop = RNA_struct_type_find_property(ot->srna, "deselect_all"); + BLI_assert(prop != NULL); + RNA_def_property_boolean_default(prop, true); + + RNA_def_boolean(ot->srna, + "selectable_only", + true, + "Selectable Only", + "Only allow selectable objects to influence raycast result"); + RNA_def_float(ot->srna, + "distance", + BVH_RAYCAST_DIST_MAX, + 0.0, + BVH_RAYCAST_DIST_MAX, + "", + "Maximum raycast distance", + 0.0, + BVH_RAYCAST_DIST_MAX); + RNA_def_boolean( + ot->srna, "from_viewer", false, "From Viewer", "Use viewer pose as raycast origin"); + RNA_def_float_vector(ot->srna, + "axis", + 3, + g_xr_default_raycast_axis, + -1.0f, + 1.0f, + "Axis", + "Raycast axis in controller/viewer space", + -1.0f, + 1.0f); + RNA_def_float_color(ot->srna, + "color", + 4, + g_xr_default_raycast_color, + 0.0f, + 1.0f, + "Color", + "Raycast color", + 0.0f, + 1.0f); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name XR Transform Grab + * + * Transforms selected objects relative to an XR controller's pose. + * \{ */ + +static int wm_xr_transform_grab_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!wm_xr_operator_test_event(op, event)) { + return OPERATOR_PASS_THROUGH; + } + + bool loc_lock, rot_lock, scale_lock; + float loc_t, rot_t, loc_ofs_orig[3], rot_ofs_orig[4]; + bool loc_ofs_set = false; + bool rot_ofs_set = false; + + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "location_lock"); + loc_lock = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + if (!loc_lock) { + prop = RNA_struct_find_property(op->ptr, "location_interpolation"); + loc_t = prop ? RNA_property_float_get(op->ptr, prop) : 0.0f; + prop = RNA_struct_find_property(op->ptr, "location_offset"); + if (prop && RNA_property_is_set(op->ptr, prop)) { + RNA_property_float_get_array(op->ptr, prop, loc_ofs_orig); + loc_ofs_set = true; + } + } + + prop = RNA_struct_find_property(op->ptr, "rotation_lock"); + rot_lock = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + if (!rot_lock) { + prop = RNA_struct_find_property(op->ptr, "rotation_interpolation"); + rot_t = prop ? RNA_property_float_get(op->ptr, prop) : 0.0f; + prop = RNA_struct_find_property(op->ptr, "rotation_offset"); + if (prop && RNA_property_is_set(op->ptr, prop)) { + float eul[3]; + RNA_property_float_get_array(op->ptr, prop, eul); + eul_to_quat(rot_ofs_orig, eul); + normalize_qt(rot_ofs_orig); + rot_ofs_set = true; + } + } + + prop = RNA_struct_find_property(op->ptr, "scale_lock"); + scale_lock = prop ? RNA_property_boolean_get(op->ptr, prop) : true; + + if (loc_lock && rot_lock && scale_lock) { + return OPERATOR_CANCELLED; + } + + const wmXrActionData *actiondata = event->customdata; + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = (obedit && (obedit->type == OB_MESH)) ? BKE_editmesh_from_object(obedit) : NULL; + bool selected = false; + + if (em) { /* TODO_XR: Non-mesh objects. */ + /* Check for selection. */ + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + BMesh *bm = em->bm; + BMIter iter; + if ((ts->selectmode & SCE_SELECT_FACE) != 0) { + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + selected = true; + break; + } + } + } + if (!selected) { + if ((ts->selectmode & SCE_SELECT_EDGE) != 0) { + BMEdge *e; + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { + selected = true; + break; + } + } + } + if (!selected) { + if ((ts->selectmode & SCE_SELECT_VERTEX) != 0) { + BMVert *v; + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + selected = true; + break; + } + } + } + } + } + } + else { + float controller_loc[3], controller_rot[4], controller_mat[4][4]; + float loc_ofs[3], loc_ofs_controller[3], rot_ofs[4], rot_ofs_controller[4], + rot_ofs_orig_inv[4]; + float q0[4], q1[4], q2[4], m0[4][4], m1[4][4], m2[4][4]; + + quat_to_mat4(controller_mat, actiondata->controller_rot); + copy_v3_v3(controller_mat[3], actiondata->controller_loc); + + /* Convert offsets to controller space. */ + if (loc_ofs_set) { + copy_v3_v3(loc_ofs_controller, loc_ofs_orig); + mul_qt_v3(actiondata->controller_rot, loc_ofs_controller); + } + if (rot_ofs_set) { + invert_qt_qt_normalized(rot_ofs_orig_inv, rot_ofs_orig); + mul_qt_qtqt(rot_ofs_controller, actiondata->controller_rot, rot_ofs_orig_inv); + normalize_qt(rot_ofs_controller); + } + + /* Apply interpolation and offsets. */ + CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { + bool update = false; + + if (ob->parent) { + invert_m4_m4(m0, ob->parentinv); + mul_m4_m4m4(m1, ob->parent->imat, controller_mat); + mul_m4_m4m4(m2, m0, m1); + mat4_to_loc_quat(controller_loc, controller_rot, m2); + + if (loc_ofs_set) { + copy_v3_v3(loc_ofs, loc_ofs_orig); + mul_qt_v3(controller_rot, loc_ofs); + } + if (rot_ofs_set) { + mul_qt_qtqt(rot_ofs, controller_rot, rot_ofs_orig_inv); + normalize_qt(rot_ofs); + } + } + else { + copy_v3_v3(controller_loc, actiondata->controller_loc); + copy_qt_qt(controller_rot, actiondata->controller_rot); + + if (loc_ofs_set) { + copy_v3_v3(loc_ofs, loc_ofs_controller); + } + if (rot_ofs_set) { + copy_qt_qt(rot_ofs, rot_ofs_controller); + } + } + + if (!loc_lock) { + if (loc_t > 0.0f) { + ob->loc[0] += loc_t * (controller_loc[0] - ob->loc[0]); + ob->loc[1] += loc_t * (controller_loc[1] - ob->loc[1]); + ob->loc[2] += loc_t * (controller_loc[2] - ob->loc[2]); + update = true; + } + if (loc_ofs_set) { + add_v3_v3(ob->loc, loc_ofs); + update = true; + } + } + + if (!rot_lock) { + if (rot_t > 0.0f) { + eul_to_quat(q1, ob->rot); + interp_qt_qtqt(q0, q1, controller_rot, rot_t); + if (!rot_ofs_set) { + quat_to_eul(ob->rot, q0); + } + update = true; + } + else if (rot_ofs_set) { + eul_to_quat(q0, ob->rot); + } + if (rot_ofs_set) { + rotation_between_quats_to_quat(q1, rot_ofs, q0); + mul_qt_qtqt(q0, rot_ofs, q1); + normalize_qt(q0); + mul_qt_qtqt(q2, controller_rot, q1); + normalize_qt(q2); + rotation_between_quats_to_quat(q1, q0, q2); + + mul_qt_qtqt(q2, q0, q1); + normalize_qt(q2); + quat_to_eul(ob->rot, q2); + update = true; + } + } + + if (update) { + DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); + } + + selected = true; + } + CTX_DATA_END; + } + + if (!selected) { + return OPERATOR_CANCELLED; + } + + wm_xr_grab_init(op); + wm_xr_grab_update(op, actiondata); + + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; +} + +static int wm_xr_transform_grab_exec(bContext *UNUSED(C), wmOperator *UNUSED(op)) +{ + return OPERATOR_CANCELLED; +} + +static int wm_xr_transform_grab_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!wm_xr_operator_test_event(op, event)) { + return OPERATOR_PASS_THROUGH; + } + + const wmXrActionData *actiondata = event->customdata; + XrGrabData *data = op->customdata; + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = (obedit && (obedit->type == OB_MESH)) ? BKE_editmesh_from_object(obedit) : NULL; + wmWindowManager *wm = CTX_wm_manager(C); + bScreen *screen_anim = ED_screen_animation_playing(wm); + bool loc_lock, rot_lock, scale_lock; + bool selected = false; + float delta[4][4]; + + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "location_lock"); + loc_lock = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + prop = RNA_struct_find_property(op->ptr, "rotation_lock"); + rot_lock = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + prop = RNA_struct_find_property(op->ptr, "scale_lock"); + scale_lock = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + + const bool do_bimanual = (actiondata->bimanual && data->bimanual_prev); + const bool apply_transform = do_bimanual ? !(loc_lock && rot_lock && scale_lock) : + !(loc_lock && rot_lock); + + if (em) { /* TODO_XR: Non-mesh objects. */ + if (apply_transform) { + ToolSettings *ts = scene->toolsettings; + BMesh *bm = em->bm; + BMIter iter; + + if (do_bimanual) { + wm_xr_grab_compute_bimanual(actiondata, + data, + obedit, + NULL, + NULL, + false, + loc_lock, + false, + rot_lock, + false, + scale_lock, + delta); + } + else { + wm_xr_grab_compute( + actiondata, data, obedit, NULL, NULL, false, loc_lock, false, rot_lock, false, delta); + } + + if ((ts->selectmode & SCE_SELECT_VERTEX) != 0) { + BMVert *v; + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT) && + !BM_elem_flag_test(v, BM_ELEM_INTERNAL_TAG)) { + mul_m4_v3(delta, v->co); + BM_elem_flag_enable(v, BM_ELEM_INTERNAL_TAG); + } + } + } + if ((ts->selectmode & SCE_SELECT_EDGE) != 0) { + BMEdge *e; + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { + if (!BM_elem_flag_test(e->v1, BM_ELEM_INTERNAL_TAG)) { + mul_m4_v3(delta, e->v1->co); + BM_elem_flag_enable(e->v1, BM_ELEM_INTERNAL_TAG); + } + if (!BM_elem_flag_test(e->v2, BM_ELEM_INTERNAL_TAG)) { + mul_m4_v3(delta, e->v2->co); + BM_elem_flag_enable(e->v2, BM_ELEM_INTERNAL_TAG); + } + } + } + } + if ((ts->selectmode & SCE_SELECT_FACE) != 0) { + BMFace *f; + BMLoop *l; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + l = f->l_first; + for (int i = 0; i < f->len; ++i, l = l->next) { + if (!BM_elem_flag_test(l->v, BM_ELEM_INTERNAL_TAG)) { + mul_m4_v3(delta, l->v->co); + BM_elem_flag_enable(l->v, BM_ELEM_INTERNAL_TAG); + } + } + } + } + } + + BM_mesh_elem_hflag_disable_all(bm, BM_VERT, BM_ELEM_INTERNAL_TAG, false); + EDBM_mesh_normals_update(em); + DEG_id_tag_update(&obedit->id, ID_RECALC_GEOMETRY); + } + + selected = true; + } + else { + float out[4][4], m0[4][4], m1[4][4]; + + if (apply_transform) { + if (do_bimanual) { + wm_xr_grab_compute_bimanual(actiondata, + data, + NULL, + NULL, + NULL, + false, + loc_lock, + false, + rot_lock, + false, + scale_lock, + delta); + } + else { + wm_xr_grab_compute( + actiondata, data, NULL, NULL, NULL, false, loc_lock, false, rot_lock, false, delta); + } + } + + CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { + if (apply_transform) { + mul_m4_m4m4(out, delta, ob->obmat); + + if (ob->parent) { + invert_m4_m4(m0, ob->parentinv); + mul_m4_m4m4(m1, ob->parent->imat, out); + mul_m4_m4m4(out, m0, m1); + } + + if (!loc_lock) { + copy_v3_v3(ob->loc, out[3]); + } + if (!rot_lock) { + mat4_to_eul(ob->rot, out); + } + if (!scale_lock && do_bimanual) { + mat4_to_size(ob->scale, out); + } + + DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); + } + + if (screen_anim && autokeyframe_cfra_can_key(scene, &ob->id)) { + wm_xr_mocap_object_autokey(C, scene, view_layer, NULL, ob, true); + } + + selected = true; + } + CTX_DATA_END; + } + + wm_xr_grab_update(op, actiondata); + + if (!selected || (event->val == KM_RELEASE)) { + wm_xr_grab_uninit(op); + + if (obedit && em) { + WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); + } + else { + WM_event_add_notifier(C, NC_SCENE | ND_TRANSFORM_DONE, scene); + } + return OPERATOR_FINISHED; + } + else if (event->val == KM_PRESS) { + return OPERATOR_RUNNING_MODAL; + } + + /* XR events currently only support press and release. */ + BLI_assert(false); + wm_xr_grab_uninit(op); + return OPERATOR_CANCELLED; +} + +static void WM_OT_xr_transform_grab(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "XR Transform Grab"; + ot->idname = "WM_OT_xr_transform_grab"; + ot->description = "Transform selected objects relative to a VR controller's pose"; + + /* callbacks */ + ot->invoke = wm_xr_transform_grab_invoke; + ot->exec = wm_xr_transform_grab_exec; + ot->modal = wm_xr_transform_grab_modal; + ot->poll = wm_xr_operator_sessionactive; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* properties */ + static const float default_offset[3] = {0}; + + RNA_def_boolean( + ot->srna, "location_lock", false, "Lock Location", "Preserve objects' original location"); + RNA_def_float(ot->srna, + "location_interpolation", + 0.0f, + 0.0f, + 1.0f, + "Location Interpolation", + "Interpolation factor between object and controller locations", + 0.0f, + 1.0f); + RNA_def_float_translation(ot->srna, + "location_offset", + 3, + default_offset, + -FLT_MAX, + FLT_MAX, + "Location Offset", + "Additional location offset in controller space", + -FLT_MAX, + FLT_MAX); + RNA_def_boolean( + ot->srna, "rotation_lock", false, "Lock Rotation", "Preserve objects' original rotation"); + RNA_def_float(ot->srna, + "rotation_interpolation", + 0.0f, + 0.0f, + 1.0f, + "Rotation Interpolation", + "Interpolation factor between object and controller rotations", + 0.0f, + 1.0f); + RNA_def_float_rotation(ot->srna, + "rotation_offset", + 3, + default_offset, + -2 * M_PI, + 2 * M_PI, + "Rotation Offset", + "Additional rotation offset in controller space", + -2 * M_PI, + 2 * M_PI); + RNA_def_boolean(ot->srna, "scale_lock", false, "Lock Scale", "Preserve objects' original scale"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name XR Motion Capture Objects Toggle + * + * Toggles enabled/auto key behavior for XR motion capture objects. + * \{ */ + +static void wm_xr_mocap_object_toggle(wmXrData *xr, + XrMotionCaptureObject *mocap_ob, + bool enable, + bool autokey) +{ + eXrMotionCaptureFlag *flag = (eXrMotionCaptureFlag *)&mocap_ob->flag; + + if (enable) { + const bool disabled_prev = (*flag & XR_MOCAP_OBJECT_ENABLE) == 0; + SET_FLAG_FROM_TEST(*flag, disabled_prev, XR_MOCAP_OBJECT_ENABLE); + + if (disabled_prev) { + /* Now enabled -> store object's original pose. */ + WM_xr_session_state_mocap_pose_set(xr, mocap_ob); + } + else { + /* Now disabled -> restore object's original pose. */ + WM_xr_session_state_mocap_pose_get(xr, mocap_ob); + } + } + + if (autokey) { + SET_FLAG_FROM_TEST(*flag, (*flag & XR_MOCAP_OBJECT_AUTOKEY) == 0, XR_MOCAP_OBJECT_AUTOKEY); + } +} + +static int wm_xr_mocap_objects_toggle_exec(bContext *C, wmOperator *op) +{ + wmWindowManager *wm = CTX_wm_manager(C); + wmXrData *xr = &wm->xr; + bool enable, autokey; + + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "enable"); + enable = prop ? RNA_property_boolean_get(op->ptr, prop) : true; + + prop = RNA_struct_find_property(op->ptr, "autokey"); + autokey = prop ? RNA_property_boolean_get(op->ptr, prop) : false; + + LISTBASE_FOREACH (XrMotionCaptureObject *, mocap_ob, &xr->session_settings.mocap_objects) { + wm_xr_mocap_object_toggle(xr, mocap_ob, enable, autokey); + } + + WM_event_add_notifier(C, NC_WM | ND_XR_DATA_CHANGED, NULL); + + return OPERATOR_FINISHED; +} + +static void WM_OT_xr_mocap_objects_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "XR Motion Capture Objects Toggle"; + ot->idname = "WM_OT_xr_mocap_objects_toggle"; + ot->description = "Toggle bindings/auto keying for VR motion capture objects"; + + /* callbacks */ + ot->exec = wm_xr_mocap_objects_toggle_exec; + ot->poll = wm_xr_operator_sessionactive; + + /* properties */ + RNA_def_boolean(ot->srna, "enable", true, "Enable", "Toggle object bindings"); + RNA_def_boolean(ot->srna, "autokey", false, "Auto Key", "Toggle auto keying"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Operator Registration + * \{ */ + +void wm_xr_operatortypes_register(void) +{ + WM_operatortype_append(WM_OT_xr_session_toggle); + WM_operatortype_append(WM_OT_xr_navigation_grab); + WM_operatortype_append(WM_OT_xr_navigation_fly); + WM_operatortype_append(WM_OT_xr_navigation_teleport); + WM_operatortype_append(WM_OT_xr_navigation_reset); + WM_operatortype_append(WM_OT_xr_select_raycast); + WM_operatortype_append(WM_OT_xr_transform_grab); + WM_operatortype_append(WM_OT_xr_mocap_objects_toggle); +} + +/** \} */ diff --git a/source/blender/windowmanager/xr/intern/wm_xr_session.c b/source/blender/windowmanager/xr/intern/wm_xr_session.c index 88bf3ff453c..845c0fa20b4 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_session.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_session.c @@ -21,8 +21,10 @@ #include "BKE_callbacks.h" #include "BKE_context.h" #include "BKE_global.h" +#include "BKE_idprop.h" #include "BKE_main.h" #include "BKE_scene.h" +#include "BKE_screen.h" #include "BLI_listbase.h" #include "BLI_math.h" @@ -30,18 +32,26 @@ #include "DEG_depsgraph.h" #include "DNA_camera_types.h" +#include "DNA_object_types.h" #include "DRW_engine.h" +#include "ED_screen.h" +#include "ED_space_api.h" + #include "GHOST_C-api.h" +#include "GPU_batch.h" #include "GPU_viewport.h" #include "MEM_guardedalloc.h" +#include "PIL_time.h" + #include "WM_api.h" #include "WM_types.h" +#include "wm_event_system.h" #include "wm_surface.h" #include "wm_window.h" #include "wm_xr_intern.h" @@ -56,21 +66,45 @@ static void wm_xr_session_create_cb(void) Main *bmain = G_MAIN; wmWindowManager *wm = bmain->wm.first; wmXrData *xr_data = &wm->xr; + XrSessionSettings *settings = &xr_data->session_settings; + wmXrSessionState *state = &xr_data->runtime->session_state; /* Get action set data from Python. */ BKE_callback_exec_null(bmain, BKE_CB_EVT_XR_SESSION_START_PRE); wm_xr_session_actions_init(xr_data); + + /* Store motion capture object poses. */ + wm_xr_mocap_orig_poses_store(settings, state); + + /* Initialize navigation. */ + WM_xr_session_state_navigation_reset(state); + if (settings->base_scale < 0.001f) { + settings->base_scale = 1.0f; + } + state->prev_base_scale = settings->base_scale; } static void wm_xr_session_controller_data_free(wmXrSessionState *state) { - BLI_freelistN(&state->controllers); + ListBase *lb = &state->controllers; + wmXrController *c; + + while ((c = BLI_pophead(lb))) { + if (c->model) { + GPU_batch_discard(c->model); + } + BLI_freelinkN(lb, c); + } } void wm_xr_session_data_free(wmXrSessionState *state) { + BLI_freelistN(&state->eyes); + wm_xr_session_controller_data_free(state); + + BLI_freelistN(&state->mocap_orig_poses); } static void wm_xr_session_exit_cb(void *customdata) @@ -80,7 +114,13 @@ static void wm_xr_session_exit_cb(void *customdata) return; } - xr_data->runtime->session_state.is_started = false; + XrSessionSettings *settings = &xr_data->session_settings; + wmXrSessionState *state = &xr_data->runtime->session_state; + + state->is_started = false; + + /* Restore motion capture object poses. */ + wm_xr_mocap_orig_poses_restore(state, settings); if (xr_data->runtime->exit_fn) { xr_data->runtime->exit_fn(xr_data); @@ -111,6 +151,7 @@ void wm_xr_session_toggle(wmWindowManager *wm, if (WM_xr_session_exists(xr_data)) { GHOST_XrSessionEnd(xr_data->runtime->context); + xr_data->runtime->session_state.is_started = false; } else { GHOST_XrSessionBeginInfo begin_info; @@ -148,7 +189,8 @@ bool WM_xr_session_is_ready(const wmXrData *xr) static void wm_xr_session_base_pose_calc(const Scene *scene, const XrSessionSettings *settings, - GHOST_XrPose *r_base_pose) + GHOST_XrPose *r_base_pose, + float *r_base_scale) { const Object *base_pose_object = ((settings->base_pose_type == XR_BASE_POSE_OBJECT) && settings->base_pose_object) ? @@ -179,6 +221,8 @@ static void wm_xr_session_base_pose_calc(const Scene *scene, copy_v3_fl(r_base_pose->position, 0.0f); axis_angle_to_quat_single(r_base_pose->orientation_quat, 'X', M_PI_2); } + + *r_base_scale = settings->base_scale; } static void wm_xr_session_draw_data_populate(wmXrData *xr_data, @@ -194,7 +238,8 @@ static void wm_xr_session_draw_data_populate(wmXrData *xr_data, r_draw_data->xr_data = xr_data; r_draw_data->surface_data = g_xr_surface->customdata; - wm_xr_session_base_pose_calc(r_draw_data->scene, settings, &r_draw_data->base_pose); + wm_xr_session_base_pose_calc( + r_draw_data->scene, settings, &r_draw_data->base_pose, &r_draw_data->base_scale); } static wmWindow *wm_xr_session_root_window_or_fallback_get(const wmWindowManager *wm, @@ -272,7 +317,7 @@ static wmXrSessionStateEvent wm_xr_session_state_to_event(const wmXrSessionState return SESSION_STATE_EVENT_NONE; } -void wm_xr_session_draw_data_update(const wmXrSessionState *state, +void wm_xr_session_draw_data_update(wmXrSessionState *state, const XrSessionSettings *settings, const GHOST_XrDrawViewInfo *draw_view, wmXrDrawData *draw_data) @@ -300,6 +345,8 @@ void wm_xr_session_draw_data_update(const wmXrSessionState *state, else { copy_v3_fl(draw_data->eye_position_ofs, 0.0f); } + /* Reset navigation. */ + WM_xr_session_state_navigation_reset(state); break; case SESSION_STATE_EVENT_POSITION_TRACKING_TOGGLE: if (use_position_tracking) { @@ -326,40 +373,59 @@ void wm_xr_session_draw_data_update(const 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; - const bool use_position_tracking = settings->flag & XR_SESSION_USE_POSITION_TRACKING; - const bool use_absolute_tracking = settings->flag & XR_SESSION_USE_ABSOLUTE_TRACKING; - - mul_qt_qtqt(viewer_pose.orientation_quat, - draw_data->base_pose.orientation_quat, - draw_view->local_pose.orientation_quat); - copy_v3_v3(viewer_pose.position, draw_data->base_pose.position); - /* The local pose and the eye pose (which is copied from an earlier local pose) both are view - * space, so Y-up. In this case we need them in regular Z-up. */ - if (use_position_tracking) { - viewer_pose.position[0] += draw_view->local_pose.position[0]; - viewer_pose.position[1] -= draw_view->local_pose.position[2]; - viewer_pose.position[2] += draw_view->local_pose.position[1]; - } - if (!use_absolute_tracking) { - viewer_pose.position[0] -= draw_data->eye_position_ofs[0]; - viewer_pose.position[1] += draw_data->eye_position_ofs[2]; - viewer_pose.position[2] -= draw_data->eye_position_ofs[1]; - } - - copy_v3_v3(state->viewer_pose.position, viewer_pose.position); - copy_qt_qt(state->viewer_pose.orientation_quat, viewer_pose.orientation_quat); - wm_xr_pose_to_imat(&viewer_pose, state->viewer_viewmat); + 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) { + zero_v3(viewer_pose.position); + } + else { + copy_v3_v3(viewer_pose.position, draw_view->local_pose.position); + } + if ((settings->flag & XR_SESSION_USE_ABSOLUTE_TRACKING) == 0) { + sub_v3_v3(viewer_pose.position, draw_data->eye_position_ofs); + } + wm_xr_pose_to_mat(&viewer_pose, viewer_mat); + + /* Apply base pose and navigation. */ + wm_xr_pose_scale_to_mat(&draw_data->base_pose, draw_data->base_scale, base_mat); + wm_xr_pose_scale_to_mat(&state->nav_pose_prev, state->nav_scale_prev, nav_mat); + mul_m4_m4m4(state->viewer_mat_base, base_mat, viewer_mat); + mul_m4_m4m4(viewer_mat, nav_mat, state->viewer_mat_base); + + /* Save final viewer pose and viewmat. */ + mat4_to_loc_quat(state->viewer_pose.position, state->viewer_pose.orientation_quat, viewer_mat); + wm_xr_pose_scale_to_imat( + &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)); + state->prev_base_scale = draw_data->base_scale; memcpy(&state->prev_local_pose, &draw_view->local_pose, sizeof(state->prev_local_pose)); + copy_v3_v3(state->prev_eye_position_ofs, draw_data->eye_position_ofs); + state->prev_settings_flag = settings->flag; state->prev_base_pose_type = settings->base_pose_type; state->prev_base_pose_object = settings->base_pose_object; @@ -391,22 +457,38 @@ bool WM_xr_session_state_viewer_pose_rotation_get(const wmXrData *xr, float r_ro return false; } - copy_v4_v4(r_rotation, xr->runtime->session_state.viewer_pose.orientation_quat); + copy_qt_qt(r_rotation, xr->runtime->session_state.viewer_pose.orientation_quat); return true; } bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr, + bool from_selection_eye, float r_viewmat[4][4], - float *r_focal_len) + float *r_focal_len, + float *r_clip_start, + float *r_clip_end) { - if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set) { + if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set || + (xr->session_settings.selection_eye >= + BLI_listbase_count(&xr->runtime->session_state.eyes))) { unit_m4(r_viewmat); - *r_focal_len = 0.0f; + *r_focal_len = *r_clip_start = *r_clip_end = 0.0f; return false; } - copy_m4_m4(r_viewmat, xr->runtime->session_state.viewer_viewmat); - *r_focal_len = xr->runtime->session_state.focal_len; + const wmXrEye *eye = BLI_findlink(&xr->runtime->session_state.eyes, + xr->session_settings.selection_eye); + BLI_assert(eye); + if (from_selection_eye) { + copy_m4_m4(r_viewmat, eye->viewmat); + } + else { + copy_m4_m4(r_viewmat, xr->runtime->session_state.viewer_viewmat); + } + /* Since eye centroid does not have a focal length, just take it from selection eye. */ + *r_focal_len = eye->focal_len; + *r_clip_start = xr->session_settings.clip_start; + *r_clip_end = xr->session_settings.clip_end; return true; } @@ -479,6 +561,74 @@ bool WM_xr_session_state_controller_aim_rotation_get(const wmXrData *xr, return true; } +bool WM_xr_session_state_nav_location_get(const wmXrData *xr, float r_location[3]) +{ + if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set) { + zero_v3(r_location); + return false; + } + + copy_v3_v3(r_location, xr->runtime->session_state.nav_pose.position); + return true; +} + +void WM_xr_session_state_nav_location_set(wmXrData *xr, const float location[3]) +{ + if (WM_xr_session_exists(xr)) { + copy_v3_v3(xr->runtime->session_state.nav_pose.position, location); + xr->runtime->session_state.is_navigation_dirty = true; + } +} + +bool WM_xr_session_state_nav_rotation_get(const wmXrData *xr, float r_rotation[4]) +{ + if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set) { + unit_qt(r_rotation); + return false; + } + + copy_qt_qt(r_rotation, xr->runtime->session_state.nav_pose.orientation_quat); + return true; +} + +void WM_xr_session_state_nav_rotation_set(wmXrData *xr, const float rotation[4]) +{ + if (WM_xr_session_exists(xr)) { + BLI_ASSERT_UNIT_QUAT(rotation); + copy_qt_qt(xr->runtime->session_state.nav_pose.orientation_quat, rotation); + xr->runtime->session_state.is_navigation_dirty = true; + } +} + +bool WM_xr_session_state_nav_scale_get(const wmXrData *xr, float *r_scale) +{ + if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set) { + *r_scale = 1.0f; + return false; + } + + *r_scale = xr->runtime->session_state.nav_scale; + return true; +} + +void WM_xr_session_state_nav_scale_set(wmXrData *xr, float scale) +{ + if (WM_xr_session_exists(xr)) { + /* Clamp to reasonable values. */ + CLAMP(scale, 0.001f, 1000.0f); + xr->runtime->session_state.nav_scale = scale; + xr->runtime->session_state.is_navigation_dirty = true; + } +} + +void WM_xr_session_state_navigation_reset(wmXrSessionState *state) +{ + zero_v3(state->nav_pose.position); + unit_qt(state->nav_pose.orientation_quat); + state->nav_scale = 1.0f; + state->is_navigation_dirty = true; +} + /* -------------------------------------------------------------------- */ /** \name XR-Session Actions * @@ -498,31 +648,43 @@ void wm_xr_session_actions_init(wmXrData *xr) static void wm_xr_session_controller_pose_calc(const GHOST_XrPose *raw_pose, const float view_ofs[3], const float base_mat[4][4], + const float nav_mat[4][4], GHOST_XrPose *r_pose, - float r_mat[4][4]) + float r_mat[4][4], + float r_mat_base[4][4]) { float m[4][4]; /* Calculate controller matrix in world space. */ wm_xr_pose_to_mat(raw_pose, m); - /* Apply eye position and base pose offsets. */ + /* Apply eye position offset. */ sub_v3_v3(m[3], view_ofs); - mul_m4_m4m4(r_mat, base_mat, m); + + /* Apply base pose and navigation. */ + mul_m4_m4m4(r_mat_base, base_mat, m); + mul_m4_m4m4(r_mat, nav_mat, r_mat_base); /* Save final pose. */ mat4_to_loc_quat(r_pose->position, r_pose->orientation_quat, r_mat); } -static void wm_xr_session_controller_data_update(const XrSessionSettings *settings, - const wmXrAction *grip_action, +static void wm_xr_session_controller_data_update(const wmXrAction *grip_action, const wmXrAction *aim_action, - wmXrSessionState *state) + bContext *C, + XrSessionSettings *settings, + GHOST_XrContextHandle xr_context, + wmXrSessionState *state, + wmWindow *win) { BLI_assert(grip_action->count_subaction_paths == aim_action->count_subaction_paths); BLI_assert(grip_action->count_subaction_paths == BLI_listbase_count(&state->controllers)); + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + wmWindowManager *wm = CTX_wm_manager(C); + bScreen *screen_anim = ED_screen_animation_playing(wm); unsigned int subaction_idx = 0; - float view_ofs[3], base_mat[4][4]; + float view_ofs[3], base_mat[4][4], nav_mat[4][4]; if ((settings->flag & XR_SESSION_USE_POSITION_TRACKING) == 0) { copy_v3_v3(view_ofs, state->prev_local_pose.position); @@ -534,44 +696,634 @@ static void wm_xr_session_controller_data_update(const XrSessionSettings *settin add_v3_v3(view_ofs, state->prev_eye_position_ofs); } - wm_xr_pose_to_mat(&state->prev_base_pose, base_mat); + wm_xr_pose_scale_to_mat(&state->prev_base_pose, state->prev_base_scale, base_mat); + wm_xr_pose_scale_to_mat(&state->nav_pose, state->nav_scale, nav_mat); LISTBASE_FOREACH_INDEX (wmXrController *, controller, &state->controllers, subaction_idx) { wm_xr_session_controller_pose_calc(&((GHOST_XrPose *)grip_action->states)[subaction_idx], view_ofs, base_mat, + nav_mat, &controller->grip_pose, - controller->grip_mat); + controller->grip_mat, + controller->grip_mat_base); wm_xr_session_controller_pose_calc(&((GHOST_XrPose *)aim_action->states)[subaction_idx], view_ofs, base_mat, + nav_mat, &controller->aim_pose, - controller->aim_mat); + controller->aim_mat, + controller->aim_mat_base); + + /* Update motion capture objects. */ + wm_xr_mocap_objects_update(controller->subaction_path, + &controller->grip_pose, + C, + settings, + scene, + view_layer, + win, + screen_anim, + (subaction_idx == 0) ? true : false); + + /* Update controller model. */ + if (!controller->model) { + /* Notify GHOST to load/continue loading the controller model data. This can be called more + * than once since the model may not be available from the runtime yet. The batch itself will + * be created in wm_xr_draw_controllers(). */ + GHOST_XrLoadControllerModel(xr_context, controller->subaction_path); + } + else { + GHOST_XrUpdateControllerModelComponents(xr_context, controller->subaction_path); + } + } +} + +static const GHOST_XrPose *wm_xr_session_controller_aim_pose_find(const wmXrSessionState *state, + const char *subaction_path) +{ + const wmXrController *controller = BLI_findstring( + &state->controllers, subaction_path, offsetof(wmXrController, subaction_path)); + return controller ? &controller->aim_pose : NULL; +} + +BLI_INLINE bool test_float_state(const float *state, float threshold, eXrAxisFlag flag) +{ + if ((flag & XR_AXIS0_POS) != 0) { + if (*state > threshold) { + return true; + } + } + else if ((flag & XR_AXIS0_NEG) != 0) { + if (*state < -threshold) { + return true; + } + } + else { + if (fabsf(*state) > threshold) { + return true; + } + } + return false; +} + +BLI_INLINE bool test_vec2f_state(const float state[2], float threshold, eXrAxisFlag flag) +{ + if ((flag & XR_AXIS0_POS) != 0) { + if (state[0] < 0.0f) { + return false; + } + } + else if ((flag & XR_AXIS0_NEG) != 0) { + if (state[0] > 0.0f) { + return false; + } + } + if ((flag & XR_AXIS1_POS) != 0) { + if (state[1] < 0.0f) { + return false; + } + } + else if ((flag & XR_AXIS1_NEG) != 0) { + if (state[1] > 0.0f) { + return false; + } + } + return (len_v2(state) > threshold); +} + +static bool wm_xr_session_modal_action_test(const ListBase *active_modal_actions, + const wmXrAction *action, + bool *r_found) +{ + if (r_found) { + *r_found = false; + } + + LISTBASE_FOREACH (LinkData *, ld, active_modal_actions) { + wmXrAction *active_modal_action = ld->data; + if (action == active_modal_action) { + if (r_found) { + *r_found = true; + } + return true; + } + if (action->ot == active_modal_action->ot && + IDP_EqualsProperties(action->op_properties, active_modal_action->op_properties)) { + /* Don't allow duplicate modal operators since this can lead to unwanted modal handler + * behavior. */ + return false; + } + } + + return true; +} + +static void wm_xr_session_modal_action_test_add(ListBase *active_modal_actions, + const wmXrAction *action) +{ + bool found; + if (wm_xr_session_modal_action_test(active_modal_actions, action, &found) && !found) { + LinkData *ld = MEM_callocN(sizeof(LinkData), __func__); + ld->data = (void *)action; + BLI_addtail(active_modal_actions, ld); + } +} + +static void wm_xr_session_modal_action_remove(ListBase *active_modal_actions, + const wmXrAction *action) +{ + LISTBASE_FOREACH (LinkData *, ld, active_modal_actions) { + if (action == ld->data) { + BLI_freelinkN(active_modal_actions, ld); + return; + } + } +} + +static wmXrHapticAction *wm_xr_session_haptic_action_find(ListBase *active_haptic_actions, + const wmXrAction *action, + const char *subaction_path) +{ + LISTBASE_FOREACH (wmXrHapticAction *, ha, active_haptic_actions) { + if ((action == ha->action) && (subaction_path == ha->subaction_path)) { + return ha; + } + } + + return NULL; +} + +static void wm_xr_session_haptic_action_add(ListBase *active_haptic_actions, + const wmXrAction *action, + const char *subaction_path, + int64_t time_now) +{ + wmXrHapticAction *ha = wm_xr_session_haptic_action_find( + active_haptic_actions, action, subaction_path); + if (ha) { + /* Reset start time since OpenXR restarts haptics if they are already active. */ + ha->time_start = time_now; + } + else { + ha = MEM_callocN(sizeof(wmXrHapticAction), __func__); + ha->action = (wmXrAction *)action; + ha->subaction_path = subaction_path; + ha->time_start = time_now; + BLI_addtail(active_haptic_actions, ha); + } +} + +static void wm_xr_session_haptic_action_remove(ListBase *active_haptic_actions, + const wmXrAction *action) +{ + LISTBASE_FOREACH (wmXrHapticAction *, ha, active_haptic_actions) { + if (action == ha->action) { + BLI_freelinkN(active_haptic_actions, ha); + return; + } + } +} + +static void wm_xr_session_haptic_timers_check(ListBase *active_haptic_actions, int64_t time_now) +{ + wmXrHapticAction *ha = active_haptic_actions->first; + while (ha) { + if (time_now - ha->time_start >= ha->action->haptic_duration) { + wmXrHapticAction *_ha = ha; + ha = ha->next; + BLI_freelinkN(active_haptic_actions, _ha); + } + else { + ha = ha->next; + } + } +} + +static void wm_xr_session_action_states_interpret(wmXrData *xr, + const char *action_set_name, + wmXrAction *action, + unsigned int subaction_idx, + ListBase *active_modal_actions, + ListBase *active_haptic_actions, + int64_t time_now, + bool modal, + bool haptic, + short *r_val) +{ + const char *haptic_subaction_path = ((action->haptic_flag & XR_HAPTIC_MATCHUSERPATHS) != 0) ? + action->subaction_paths[subaction_idx] : + NULL; + bool curr = false; + bool prev = false; + + switch (action->type) { + case XR_BOOLEAN_INPUT: { + const bool *state = &((bool *)action->states)[subaction_idx]; + bool *state_prev = &((bool *)action->states_prev)[subaction_idx]; + if (*state) { + curr = true; + } + if (*state_prev) { + prev = true; + } + *state_prev = *state; + break; + } + case XR_FLOAT_INPUT: { + const float *state = &((float *)action->states)[subaction_idx]; + float *state_prev = &((float *)action->states_prev)[subaction_idx]; + if (test_float_state( + state, action->float_thresholds[subaction_idx], action->axis_flags[subaction_idx])) { + curr = true; + } + if (test_float_state(state_prev, + action->float_thresholds[subaction_idx], + action->axis_flags[subaction_idx])) { + prev = true; + } + *state_prev = *state; + break; + } + case XR_VECTOR2F_INPUT: { + const float(*state)[2] = &((float(*)[2])action->states)[subaction_idx]; + float(*state_prev)[2] = &((float(*)[2])action->states_prev)[subaction_idx]; + if (test_vec2f_state(*state, + action->float_thresholds[subaction_idx], + action->axis_flags[subaction_idx])) { + curr = true; + } + if (test_vec2f_state(*state_prev, + action->float_thresholds[subaction_idx], + action->axis_flags[subaction_idx])) { + prev = true; + } + copy_v2_v2(*state_prev, *state); + break; + } + case XR_POSE_INPUT: + case XR_VIBRATION_OUTPUT: + BLI_assert_unreachable(); + break; + } + + if (curr) { + if (!prev) { + if (modal || (action->op_flag == XR_OP_PRESS)) { + *r_val = KM_PRESS; + } + if (haptic && (action->haptic_flag & (XR_HAPTIC_PRESS | XR_HAPTIC_REPEAT)) != 0) { + /* Apply haptics. */ + if (WM_xr_haptic_action_apply(xr, + action_set_name, + action->haptic_name, + haptic_subaction_path, + &action->haptic_duration, + &action->haptic_frequency, + &action->haptic_amplitude)) { + wm_xr_session_haptic_action_add( + active_haptic_actions, action, haptic_subaction_path, time_now); + } + } + } + else if (modal) { + *r_val = KM_PRESS; + } + if (modal && !action->active_modal_path) { + /* Set active modal path. */ + action->active_modal_path = action->subaction_paths[subaction_idx]; + /* Add to active modal actions. */ + wm_xr_session_modal_action_test_add(active_modal_actions, action); + } + if (haptic && ((action->haptic_flag & XR_HAPTIC_REPEAT) != 0)) { + if (!wm_xr_session_haptic_action_find( + active_haptic_actions, action, haptic_subaction_path)) { + /* Apply haptics. */ + if (WM_xr_haptic_action_apply(xr, + action_set_name, + action->haptic_name, + haptic_subaction_path, + &action->haptic_duration, + &action->haptic_frequency, + &action->haptic_amplitude)) { + wm_xr_session_haptic_action_add( + active_haptic_actions, action, haptic_subaction_path, time_now); + } + } + } + } + else if (prev) { + if (modal || (action->op_flag == XR_OP_RELEASE)) { + *r_val = KM_RELEASE; + if (modal && (action->subaction_paths[subaction_idx] == action->active_modal_path)) { + /* Unset active modal path. */ + action->active_modal_path = NULL; + /* Remove from active modal actions. */ + wm_xr_session_modal_action_remove(active_modal_actions, action); + } + } + if (haptic) { + if ((action->haptic_flag & XR_HAPTIC_RELEASE) != 0) { + /* Apply haptics. */ + if (WM_xr_haptic_action_apply(xr, + action_set_name, + action->haptic_name, + haptic_subaction_path, + &action->haptic_duration, + &action->haptic_frequency, + &action->haptic_amplitude)) { + wm_xr_session_haptic_action_add( + active_haptic_actions, action, haptic_subaction_path, time_now); + } + } + else if ((action->haptic_flag & XR_HAPTIC_REPEAT) != 0) { + /* Stop any active haptics. */ + WM_xr_haptic_action_stop(xr, action_set_name, action->haptic_name, haptic_subaction_path); + wm_xr_session_haptic_action_remove(active_haptic_actions, action); + } + } } } -void wm_xr_session_actions_update(wmXrData *xr) +static bool wm_xr_session_action_test_bimanual(const wmXrSessionState *session_state, + wmXrAction *action, + unsigned int subaction_idx, + unsigned int *r_subaction_idx_other, + const GHOST_XrPose **r_aim_pose_other) { + if ((action->action_flag & XR_ACTION_BIMANUAL) == 0) { + return false; + } + + bool bimanual = false; + + *r_subaction_idx_other = (subaction_idx == 0) ? + (unsigned int)min_ii(1, action->count_subaction_paths - 1) : + 0; + + switch (action->type) { + case XR_BOOLEAN_INPUT: { + const bool *state = &((bool *)action->states)[*r_subaction_idx_other]; + if (*state) { + bimanual = true; + } + break; + } + case XR_FLOAT_INPUT: { + const float *state = &((float *)action->states)[*r_subaction_idx_other]; + if (test_float_state(state, + action->float_thresholds[*r_subaction_idx_other], + action->axis_flags[*r_subaction_idx_other])) { + bimanual = true; + } + break; + } + case XR_VECTOR2F_INPUT: { + const float(*state)[2] = &((float(*)[2])action->states)[*r_subaction_idx_other]; + if (test_vec2f_state(*state, + action->float_thresholds[*r_subaction_idx_other], + action->axis_flags[*r_subaction_idx_other])) { + bimanual = true; + } + break; + } + case XR_POSE_INPUT: + case XR_VIBRATION_OUTPUT: + BLI_assert_unreachable(); + break; + } + + if (bimanual) { + *r_aim_pose_other = wm_xr_session_controller_aim_pose_find( + session_state, action->subaction_paths[*r_subaction_idx_other]); + } + + return bimanual; +} + +static wmXrActionData *wm_xr_session_event_create(const char *action_set_name, + const wmXrAction *action, + const wmXrEye *selection_eye, + const GHOST_XrPose *controller_aim_pose, + const GHOST_XrPose *controller_aim_pose_other, + unsigned int subaction_idx, + unsigned int subaction_idx_other, + bool bimanual) +{ + wmXrActionData *data = MEM_callocN(sizeof(wmXrActionData), __func__); + strcpy(data->action_set, action_set_name); + strcpy(data->action, action->name); + data->type = action->type; + + switch (action->type) { + case XR_BOOLEAN_INPUT: + data->state[0] = ((bool *)action->states)[subaction_idx] ? 1.0f : 0.0f; + if (bimanual) { + data->state_other[0] = ((bool *)action->states)[subaction_idx_other] ? 1.0f : 0.0f; + } + break; + case XR_FLOAT_INPUT: + data->state[0] = ((float *)action->states)[subaction_idx]; + if (bimanual) { + data->state_other[0] = ((float *)action->states)[subaction_idx_other]; + } + data->float_threshold = action->float_thresholds[subaction_idx]; + break; + case XR_VECTOR2F_INPUT: + copy_v2_v2(data->state, ((float(*)[2])action->states)[subaction_idx]); + if (bimanual) { + copy_v2_v2(data->state_other, ((float(*)[2])action->states)[subaction_idx_other]); + } + data->float_threshold = action->float_thresholds[subaction_idx]; + break; + case XR_POSE_INPUT: + case XR_VIBRATION_OUTPUT: + BLI_assert_unreachable(); + break; + } + + if (controller_aim_pose) { + copy_v3_v3(data->controller_loc, controller_aim_pose->position); + copy_qt_qt(data->controller_rot, controller_aim_pose->orientation_quat); + + if (bimanual && controller_aim_pose_other) { + copy_v3_v3(data->controller_loc_other, controller_aim_pose_other->position); + copy_qt_qt(data->controller_rot_other, controller_aim_pose_other->orientation_quat); + } + else { + data->controller_rot_other[0] = 1.0f; + } + } + else { + data->controller_rot[0] = 1.0f; + data->controller_rot_other[0] = 1.0f; + } + + if (selection_eye) { + copy_m4_m4(data->eye_viewmat, selection_eye->viewmat); + data->eye_lens = selection_eye->focal_len; + } + + data->ot = action->ot; + data->op_properties = action->op_properties; + + data->bimanual = bimanual; + + return data; +} + +/* Dispatch events to XR surface / window queues. */ +static void wm_xr_session_events_dispatch(wmXrData *xr, + const XrSessionSettings *settings, + GHOST_XrContextHandle xr_context, + wmXrActionSet *action_set, + wmXrSessionState *session_state, + wmWindow *win) +{ + const char *action_set_name = action_set->name; + + const unsigned int count = GHOST_XrGetActionCount(xr_context, action_set_name); + if (count < 1 || (settings->selection_eye >= BLI_listbase_count(&session_state->eyes))) { + return; + } + + const int64_t time_now = (int64_t)(PIL_check_seconds_timer() * 1000); + + const wmXrEye *selection_eye = BLI_findlink(&session_state->eyes, settings->selection_eye); + 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. */ + wm_xr_session_haptic_timers_check(active_haptic_actions, time_now); + + for (unsigned int action_idx = 0; action_idx < count; ++action_idx) { + wmXrAction *action = actions[action_idx]; + if (action && action->ot) { + const bool modal = (action->ot->modal || action->ot->modal_3d); + const bool haptic = (GHOST_XrGetActionCustomdata( + xr_context, action_set_name, action->haptic_name) != NULL); + + for (unsigned int subaction_idx = 0; subaction_idx < action->count_subaction_paths; + ++subaction_idx) { + short val = KM_NOTHING; + + /* 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); + + if ((val != KM_NOTHING) && + (!modal || (wm_xr_session_modal_action_test(active_modal_actions, action, NULL) && + (!action->active_modal_path || (action->subaction_paths[subaction_idx] == + action->active_modal_path))))) { + 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; + + /* 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, + selection_eye, + aim_pose, + aim_pose_other, + subaction_idx, + subaction_idx_other, + bimanual); + wm_event_add_xrevent(win, actiondata, val); + } + } + } + } + + MEM_freeN(actions); +} + +void wm_xr_session_actions_update(const bContext *C) +{ + wmWindowManager *wm = CTX_wm_manager(C); + wmXrData *xr = &wm->xr; if (!xr->runtime) { return; } + XrSessionSettings *settings = &xr->session_settings; + wmWindow *win = wm_xr_session_root_window_or_fallback_get(wm, xr->runtime); GHOST_XrContextHandle xr_context = xr->runtime->context; wmXrSessionState *state = &xr->runtime->session_state; wmXrActionSet *active_action_set = state->active_action_set; + if (state->is_navigation_dirty) { + memcpy(&state->nav_pose_prev, &state->nav_pose, sizeof(state->nav_pose_prev)); + state->nav_scale_prev = state->nav_scale; + state->is_navigation_dirty = false; + + /* Update viewer/eye poses with any navigation changes since the last actions sync so that data + * is correct for queries. */ + float m[4][4], viewer_mat[4][4]; + wm_xr_pose_scale_to_mat(&state->nav_pose, state->nav_scale, m); + mul_m4_m4m4(viewer_mat, m, state->viewer_mat_base); + 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); + } + } + + /* Update headset motion capture objects. */ + { + const char *user_path = "/user/head"; + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + bScreen *screen_anim = ED_screen_animation_playing(wm); + + wm_xr_mocap_objects_update(user_path, + &state->viewer_pose, + (bContext *)C, + settings, + scene, + view_layer, + win, + screen_anim, + true); + } + int ret = GHOST_XrSyncActions(xr_context, active_action_set ? active_action_set->name : NULL); if (!ret) { return; } - /* Only update controller data for active action set. */ + /* Only update controller data and dispatch events for active action set. */ if (active_action_set) { if (active_action_set->controller_grip_action && active_action_set->controller_aim_action) { - wm_xr_session_controller_data_update(&xr->session_settings, - active_action_set->controller_grip_action, + wm_xr_session_controller_data_update(active_action_set->controller_grip_action, active_action_set->controller_aim_action, - state); + (bContext *)C, + &xr->session_settings, + xr_context, + state, + win); + } + + if (win) { + wm_xr_session_events_dispatch(xr, settings, xr_context, active_action_set, state, win); } } } @@ -598,11 +1350,29 @@ void wm_xr_session_controller_data_populate(const wmXrAction *grip_action, BLI_addtail(controllers, controller); } + + /* Activate draw callback. */ + if (g_xr_surface) { + wmXrSurfaceData *surface_data = g_xr_surface->customdata; + if (surface_data->controller_art && !surface_data->controller_draw_handle) { + surface_data->controller_draw_handle = ED_region_draw_cb_activate( + surface_data->controller_art, wm_xr_draw_controllers, xr, REGION_DRAW_POST_VIEW); + } + } } void wm_xr_session_controller_data_clear(wmXrSessionState *state) { wm_xr_session_controller_data_free(state); + + /* Deactivate draw callback. */ + if (g_xr_surface) { + wmXrSurfaceData *surface_data = g_xr_surface->customdata; + if (surface_data->controller_art && surface_data->controller_draw_handle) { + ED_region_draw_cb_exit(surface_data->controller_art, surface_data->controller_draw_handle); + surface_data->controller_draw_handle = NULL; + } + } } /** \} */ /* XR-Session Actions */ @@ -628,7 +1398,7 @@ static void wm_xr_session_surface_draw(bContext *C) Main *bmain = CTX_data_main(C); wmXrDrawData draw_data; - if (!GHOST_XrSessionIsRunning(wm->xr.runtime->context)) { + if (!WM_xr_session_is_ready(&wm->xr)) { return; } @@ -728,6 +1498,11 @@ static void wm_xr_session_surface_free_data(wmSurface *surface) BLI_freelinkN(lb, vp); } + if (data->controller_art) { + BLI_freelistN(&data->controller_art->drawcalls); + MEM_freeN(data->controller_art); + } + MEM_freeN(surface->customdata); g_xr_surface = NULL; @@ -742,6 +1517,7 @@ static wmSurface *wm_xr_session_surface_create(void) wmSurface *surface = MEM_callocN(sizeof(*surface), __func__); wmXrSurfaceData *data = MEM_callocN(sizeof(*data), "XrSurfaceData"); + data->controller_art = MEM_callocN(sizeof(*(data->controller_art)), "XrControllerRegionType"); surface->draw = wm_xr_session_surface_draw; surface->free_data = wm_xr_session_surface_free_data; @@ -751,6 +1527,7 @@ static wmSurface *wm_xr_session_surface_create(void) surface->ghost_ctx = DRW_xr_opengl_context_get(); surface->gpu_ctx = DRW_xr_gpu_context_get(); + data->controller_art->regionid = RGN_TYPE_XR; surface->customdata = data; g_xr_surface = surface; @@ -784,4 +1561,13 @@ void wm_xr_session_gpu_binding_context_destroy(GHOST_ContextHandle UNUSED(contex WM_main_add_notifier(NC_WM | ND_XR_DATA_CHANGED, NULL); } +ARegionType *WM_xr_surface_controller_region_type_get(void) +{ + if (g_xr_surface) { + wmXrSurfaceData *data = g_xr_surface->customdata; + return data->controller_art; + } + return NULL; +} + /** \} */ /* XR-Session Surface */ diff --git a/source/blender/windowmanager/xr/wm_xr.h b/source/blender/windowmanager/xr/wm_xr.h index 0f0fbe8bc00..cfe20177d87 100644 --- a/source/blender/windowmanager/xr/wm_xr.h +++ b/source/blender/windowmanager/xr/wm_xr.h @@ -22,6 +22,7 @@ struct wmWindowManager; struct wmXrData; +struct bContext; typedef void (*wmXrSessionExitFn)(const wmXrData *xr_data); @@ -29,4 +30,7 @@ typedef void (*wmXrSessionExitFn)(const wmXrData *xr_data); bool wm_xr_init(wmWindowManager *wm); void wm_xr_exit(wmWindowManager *wm); void wm_xr_session_toggle(wmWindowManager *wm, wmWindow *win, wmXrSessionExitFn session_exit_fn); -bool wm_xr_events_handle(wmWindowManager *wm); +bool wm_xr_events_handle(const struct bContext *C); + +/* wm_xr_operators.c */ +void wm_xr_operatortypes_register(void); diff --git a/source/creator/creator.c b/source/creator/creator.c index 2ec4a2aa616..be096db6135 100644 --- a/source/creator/creator.c +++ b/source/creator/creator.c @@ -513,6 +513,10 @@ int main(int argc, CTX_py_init_set(C, true); WM_keyconfig_init(C); +#ifdef WITH_XR_OPENXR + WM_xr_actionconfig_init(C); +#endif + #ifdef WITH_FREESTYLE /* Initialize Freestyle. */ FRS_init(); |