diff options
-rw-r--r-- | source/blender/editors/include/ED_transform_snap_object_context.h | 1 | ||||
-rw-r--r-- | source/blender/editors/transform/transform_snap_object.c | 5 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_xr_types.h | 4 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_xr.c | 98 | ||||
-rw-r--r-- | source/blender/windowmanager/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/windowmanager/WM_api.h | 7 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_operators.c | 88 | ||||
-rw-r--r-- | source/blender/windowmanager/xr/intern/wm_xr_draw.c | 47 | ||||
-rw-r--r-- | source/blender/windowmanager/xr/intern/wm_xr_intern.h | 22 | ||||
-rw-r--r-- | source/blender/windowmanager/xr/intern/wm_xr_operators.c | 1537 | ||||
-rw-r--r-- | source/blender/windowmanager/xr/intern/wm_xr_session.c | 184 | ||||
-rw-r--r-- | source/blender/windowmanager/xr/wm_xr.h | 3 |
12 files changed, 1867 insertions, 130 deletions
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 7002db163b6..62d1dfbf0b1 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,7 @@ typedef enum { SNAP_NOT_SELECTED = 1, SNAP_NOT_ACTIVE = 2, SNAP_ONLY_ACTIVE = 3, + SNAP_SELECTABLE = 4, } eSnapSelect; typedef enum { diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c index f5f3fafe897..c779fbe4a33 100644 --- a/source/blender/editors/transform/transform_snap_object.c +++ b/source/blender/editors/transform/transform_snap_object.c @@ -493,6 +493,11 @@ static void iter_snap_objects(SnapObjectContext *sctx, continue; } } + else if (snap_select == SNAP_SELECTABLE) { + if (!(base->flag & BASE_SELECTABLE)) { + continue; + } + } Object *obj_eval = DEG_get_evaluated_object(sctx->runtime.depsgraph, base->object); if (obj_eval->transflag & OB_DUPLI || BKE_object_has_geometry_set_instances(obj_eval)) { diff --git a/source/blender/makesdna/DNA_xr_types.h b/source/blender/makesdna/DNA_xr_types.h index 6f4f7e3e8ae..f7da912f299 100644 --- a/source/blender/makesdna/DNA_xr_types.h +++ b/source/blender/makesdna/DNA_xr_types.h @@ -32,8 +32,8 @@ typedef struct XrSessionSettings { /** Shading settings, struct shared with 3D-View so settings are the same. */ struct View3DShading shading; - char _pad[7]; - + 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; diff --git a/source/blender/makesrna/intern/rna_xr.c b/source/blender/makesrna/intern/rna_xr.c index 3705284ca66..0f6544e6d47 100644 --- a/source/blender/makesrna/intern/rna_xr.c +++ b/source/blender/makesrna/intern/rna_xr.c @@ -892,6 +892,71 @@ static void rna_XrSessionState_viewer_pose_rotation_get(PointerRNA *ptr, float * # endif } +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); + WM_xr_session_state_nav_location_set(xr, values); +# else + UNUSED_VARS(ptr, values); +# endif +} + +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); + WM_xr_session_state_nav_rotation_get(xr, r_values); +# else + UNUSED_VARS(ptr); + unit_qt(r_values); +# endif +} + +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_session_state_nav_rotation_set(xr, values); +# else + UNUSED_VARS(ptr, values); +# endif +} + +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); + WM_xr_session_state_nav_scale_get(xr, &value); +# else + UNUSED_VARS(ptr); + value = 1.0f; +# endif + return 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_session_state_nav_scale_set(xr, value); +# else + UNUSED_VARS(ptr, value); +# endif +} + static void rna_XrSessionState_actionmaps_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) { # ifdef WITH_XR_OPENXR @@ -1615,6 +1680,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, 1e-6f, FLT_MAX); + RNA_def_property_ui_range(prop, 0.001f, FLT_MAX, 10, 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"); @@ -1981,6 +2053,32 @@ 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, "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, "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, "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", + "Additional scale multiplier to apply to base scale when determining viewer scale"); + prop = RNA_def_property(srna, "actionmaps", PROP_COLLECTION, PROP_NONE); RNA_def_property_collection_funcs(prop, "rna_XrSessionState_actionmaps_begin", diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index 4d65726fe2b..03b2fb49085 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -202,6 +202,7 @@ 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_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 2988aacc4d3..2d5100eec7c 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -1027,6 +1027,13 @@ 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); diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index 89f0206d72e..1130ad9a558 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -3759,87 +3759,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 * \{ */ @@ -3881,9 +3800,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 @@ -3891,6 +3807,10 @@ void wm_operatortypes_register(void) WM_operatortype_append(WM_OT_previews_clear); WM_operatortype_append(WM_OT_doc_view_manual_ui_context); +#ifdef WITH_XR_OPENXR + wm_xr_operatortypes_register(); +#endif + /* gizmos */ WM_operatortype_append(GIZMOGROUP_OT_gizmo_select); WM_operatortype_append(GIZMOGROUP_OT_gizmo_tweak); diff --git a/source/blender/windowmanager/xr/intern/wm_xr_draw.c b/source/blender/windowmanager/xr/intern/wm_xr_draw.c index 628d50f05bd..9829e13c677 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_draw.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_draw.c @@ -49,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]; @@ -57,15 +67,32 @@ 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_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], m[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) { @@ -76,12 +103,14 @@ static void wm_xr_draw_matrices_create(const wmXrDrawData *draw_data, } 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(m, eye_inv, base_inv); + mul_m4_m4m4(r_viewmat, m, 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, @@ -131,8 +160,8 @@ void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata) 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, winmat); + wm_xr_session_state_update(settings, draw_data, draw_view, viewmat, session_state); if (!wm_xr_session_surface_offscreen_ensure(surface_data, draw_view)) { return; diff --git a/source/blender/windowmanager/xr/intern/wm_xr_intern.h b/source/blender/windowmanager/xr/intern/wm_xr_intern.h index 2cd0ba5c056..6df70dc37aa 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_intern.h +++ b/source/blender/windowmanager/xr/intern/wm_xr_intern.h @@ -33,6 +33,8 @@ typedef struct wmXrSessionState { GHOST_XrPose viewer_pose; /** The last known view matrix, calculated from above's viewer pose. */ float viewer_viewmat[4][4]; + /** The last known viewer matrix, without navigation applied. */ + float viewer_mat_base[4][4]; float focal_len; /** Copy of XrSessionSettings.base_pose_ data to detect changes that need @@ -43,6 +45,8 @@ typedef struct wmXrSessionState { 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,6 +55,15 @@ typedef struct wmXrSessionState { bool force_reset_to_base_pose; bool is_view_data_set; + /** 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; + /** Last known controller data. */ ListBase controllers; /* #wmXrController */ @@ -106,6 +119,8 @@ 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. */ @@ -123,9 +138,11 @@ typedef struct wmXrController { /** 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; @@ -192,13 +209,14 @@ void wm_xr_runtime_data_free(wmXrRuntimeData **runtime); void wm_xr_session_data_free(wmXrSessionState *state); wmWindow *wm_xr_session_root_window_or_fallback_get(const wmWindowManager *wm, const wmXrRuntimeData *runtime_data); -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); void wm_xr_session_state_update(const XrSessionSettings *settings, const wmXrDrawData *draw_data, const GHOST_XrDrawViewInfo *draw_view, + const float viewmat[4][4], wmXrSessionState *state); bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data, const GHOST_XrDrawViewInfo *draw_view); @@ -214,6 +232,8 @@ 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); 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..652e357b6d5 --- /dev/null +++ b/source/blender/windowmanager/xr/intern/wm_xr_operators.c @@ -0,0 +1,1537 @@ +/* + * 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_global.h" +#include "BKE_idprop.h" +#include "BKE_main.h" +#include "BKE_screen.h" + +#include "DEG_depsgraph.h" + +#include "ED_screen.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; + bool loc_lock, locz_lock, rot_lock, rotz_lock, scale_lock; +} 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); +} + +/** + * Compute transformation delta for a one-handed grab interaction. + * + * \param actiondata: Contains current controller pose in world space. + * \param data: Contains previous controller pose in world space. + * + * The delta is computed as the difference between the current and previous + * controller poses i.e. delta = curr * prev^-1. + */ +static void wm_xr_grab_compute(const wmXrActionData *actiondata, + const XrGrabData *data, + const float nav_mat[4][4], + const float nav_inv[4][4], + bool reverse, + float r_delta[4][4]) +{ + const bool nav_lock = (nav_mat && nav_inv); + float prev[4][4], curr[4][4]; + + if (!data->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 (!data->loc_lock || nav_lock) { + copy_v3_v3(prev[3], data->mat_prev[3]); + copy_v3_v3(curr[3], actiondata->controller_loc); + } + + if (nav_lock) { + wm_xr_navlocks_apply( + nav_mat, nav_inv, data->loc_lock, data->locz_lock, data->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); + } +} + +/** + * Compute transformation delta for a two-handed (bimanual) grab interaction. + * + * \param actiondata: Contains current controller poses in world space. + * \param data: Contains previous controller poses in world space. + * + * The delta is computed as the difference (delta = curr * prev^-1) between the current + * and previous transformations, where the transformations themselves are determined as follows: + * - Translation: Averaged controller positions. + * - Rotation: Rotation of axis line between controllers. + * - Scale: Distance between controllers. + */ +static void wm_xr_grab_compute_bimanual(const wmXrActionData *actiondata, + const XrGrabData *data, + const float nav_mat[4][4], + const float nav_inv[4][4], + bool reverse, + 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 (!data->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 (!data->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 (!data->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 (nav_lock) { + wm_xr_navlocks_apply( + nav_mat, nav_inv, data->loc_lock, data->locz_lock, data->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 bool wm_xr_navigation_grab_can_do_bimanual(const wmXrActionData *actiondata, + const XrGrabData *data) +{ + /* Returns true if: 1) Bimanual interaction is currently occurring (i.e. inputs on both + controllers are pressed) and 2) bimanual interaction occurred on the last update. This second + part is needed to avoid "jumpy" navigation changes when transitioning from one-handed to + two-handed interaction (see #wm_xr_grab_compute/compute_bimanual() for how navigation deltas + are calculated). */ + return (actiondata->bimanual && data->bimanual_prev); +} + +static bool wm_xr_navigation_grab_is_bimanual_ending(const wmXrActionData *actiondata, + const XrGrabData *data) +{ + return (!actiondata->bimanual && data->bimanual_prev); +} + +static bool wm_xr_navigation_grab_is_locked(const XrGrabData *data, const bool bimanual) +{ + if (bimanual) { + return data->loc_lock && data->rot_lock && data->scale_lock; + } + else { + /* Ignore scale lock, as one-handed interaction cannot change navigation scale. */ + return data->loc_lock && data->rot_lock; + } +} + +static void wm_xr_navigation_grab_apply(wmXrData *xr, + const wmXrActionData *actiondata, + const XrGrabData *data, + bool bimanual) +{ + GHOST_XrPose nav_pose; + float nav_scale; + float nav_mat[4][4], nav_inv[4][4], delta[4][4], out[4][4]; + + const bool need_navinv = (data->loc_lock || data->locz_lock || data->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 (need_navinv) { + wm_xr_pose_scale_to_imat(&nav_pose, nav_scale, nav_inv); + } + + if (bimanual) { + wm_xr_grab_compute_bimanual( + actiondata, data, need_navinv ? nav_mat : NULL, need_navinv ? nav_inv : NULL, true, delta); + } + else { + wm_xr_grab_compute( + actiondata, data, need_navinv ? nav_mat : NULL, need_navinv ? nav_inv : NULL, true, delta); + } + + mul_m4_m4m4(out, delta, nav_mat); + + /* Limit scale to reasonable values. */ + nav_scale = len_v3(out[0]); + + if (!(nav_scale < xr->session_settings.clip_start || + nav_scale > xr->session_settings.clip_end)) { + WM_xr_session_state_nav_location_set(xr, out[3]); + if (!data->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 (!data->scale_lock && bimanual) { + WM_xr_session_state_nav_scale_set(xr, nav_scale); + } + } +} + +static void wm_xr_navigation_grab_bimanual_state_update(const wmXrActionData *actiondata, + XrGrabData *data) +{ + 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; + } +} + +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; + + const bool do_bimanual = wm_xr_navigation_grab_can_do_bimanual(actiondata, data); + + data->loc_lock = RNA_boolean_get(op->ptr, "lock_location"); + data->locz_lock = RNA_boolean_get(op->ptr, "lock_location_z"); + data->rot_lock = RNA_boolean_get(op->ptr, "lock_rotation"); + data->rotz_lock = RNA_boolean_get(op->ptr, "lock_rotation_z"); + data->scale_lock = RNA_boolean_get(op->ptr, "lock_scale"); + + /* Check if navigation is locked. */ + if (!wm_xr_navigation_grab_is_locked(data, do_bimanual)) { + /* Prevent unwanted snapping (i.e. "jumpy" navigation changes when transitioning from + two-handed to one-handed interaction) at the end of a bimanual interaction. */ + if (!wm_xr_navigation_grab_is_bimanual_ending(actiondata, data)) { + wm_xr_navigation_grab_apply(xr, actiondata, data, do_bimanual); + } + } + + wm_xr_navigation_grab_bimanual_state_update(actiondata, data); + + /* Note: KM_PRESS and KM_RELEASE are the only two values supported by XR events during event + dispatching (see #wm_xr_session_action_states_interpret()). For modal XR operators, modal + handling starts when an input is "pressed" (action state exceeds the action threshold) and + ends when the input is "released" (state falls below the threshold). */ + if (event->val == KM_PRESS) { + return OPERATOR_RUNNING_MODAL; + } + else if (event->val == KM_RELEASE) { + wm_xr_grab_uninit(op); + return OPERATOR_FINISHED; + } + + BLI_assert_unreachable(); + 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) { + return; + } + + ARegionType *art = BKE_regiontype_from_id(st, RGN_TYPE_XR); + if (!art) { + return; + } + + XrRaycastData *data = op->customdata; + data->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) { + return; + } + + SpaceType *st = BKE_spacetype_from_id(SPACE_VIEW3D); + if (st) { + ARegionType *art = BKE_regiontype_from_id(st, RGN_TYPE_XR); + if (art) { + XrRaycastData *data = op->customdata; + ED_region_draw_cb_exit(art, data->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]; + + data->from_viewer = RNA_boolean_get(op->ptr, "from_viewer"); + RNA_float_get_array(op->ptr, "axis", axis); + RNA_float_get_array(op->ptr, "color", data->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(Scene *scene, + Depsgraph *depsgraph, + 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(scene, 0); + + ED_transform_snap_object_project_ray_ex( + sctx, + depsgraph, + NULL, + &(const struct SnapObjectParams){ + .snap_select = (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(); + + mode = (eXrFlyMode)RNA_enum_get(op->ptr, "mode"); + turn = (mode == XR_FLY_TURNLEFT || mode == XR_FLY_TURNRIGHT); + + locz_lock = RNA_boolean_get(op->ptr, "lock_location_z"); + dir_lock = RNA_boolean_get(op->ptr, "lock_direction"); + speed_frame_based = RNA_boolean_get(op->ptr, "speed_frame_based"); + speed = RNA_float_get(op->ptr, "speed_min"); + speed_max = RNA_float_get(op->ptr, "speed_max"); + + PropertyRNA *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 base and navigation scale. */ + WM_xr_session_state_nav_scale_get(xr, &nav_scale); + speed *= xr->session_settings.base_scale * 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_unreachable(); + 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) +{ + Scene *scene = CTX_data_scene(C); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + float location[3]; + float normal[3]; + int index; + Object *ob = NULL; + float obmat[4][4]; + + wm_xr_raycast(scene, + depsgraph, + 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; + + RNA_boolean_get_array(op->ptr, "teleport_axes", teleport_axes); + teleport_t = RNA_float_get(op->ptr, "interpolation"); + teleport_ofs = RNA_float_get(op->ptr, "offset"); + selectable_only = RNA_boolean_get(op->ptr, "selectable_only"); + ray_dist = RNA_float_get(op->ptr, "distance"); + + 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_unreachable(); + 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; + + reset_loc = RNA_boolean_get(op->ptr, "location"); + reset_rot = RNA_boolean_get(op->ptr, "rotation"); + reset_scale = RNA_boolean_get(op->ptr, "scale"); + + 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 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); +} + +/** \} */ diff --git a/source/blender/windowmanager/xr/intern/wm_xr_session.c b/source/blender/windowmanager/xr/intern/wm_xr_session.c index 9f53db1347c..62757c0bddd 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_session.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_session.c @@ -66,11 +66,20 @@ static void wm_xr_session_create_cb(void) Main *bmain = G_MAIN; wmWindowManager *wm = bmain->wm.first; wmXrData *xr_data = &wm->xr; + wmXrSessionState *state = &xr_data->runtime->session_state; + XrSessionSettings *settings = &xr_data->session_settings; /* 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); + + /* Initialize navigation. */ + WM_xr_session_state_navigation_reset(state); + if (settings->base_scale < FLT_EPSILON) { + settings->base_scale = 1.0f; + } + state->prev_base_scale = settings->base_scale; } static void wm_xr_session_controller_data_free(wmXrSessionState *state) @@ -167,7 +176,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) ? @@ -198,6 +208,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, @@ -213,7 +225,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); } wmWindow *wm_xr_session_root_window_or_fallback_get(const wmWindowManager *wm, @@ -291,7 +304,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) @@ -319,6 +332,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) { @@ -345,32 +360,36 @@ 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], 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]; + + /* 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, @@ -378,7 +397,10 @@ void wm_xr_session_state_update(const XrSessionSettings *settings, 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; @@ -503,6 +525,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, xr->session_settings.clip_start, xr->session_settings.clip_end); + 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 * @@ -522,16 +612,21 @@ 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); @@ -547,7 +642,7 @@ static void wm_xr_session_controller_data_update(const XrSessionSettings *settin BLI_assert(grip_action->count_subaction_paths == BLI_listbase_count(&state->controllers)); 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); @@ -559,19 +654,24 @@ 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); if (!controller->model) { /* Notify GHOST to load/continue loading the controller model data. This can be called more @@ -1094,10 +1194,26 @@ void wm_xr_session_actions_update(wmWindowManager *wm) return; } + XrSessionSettings *settings = &xr->session_settings; 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 pose 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); + } + int ret = GHOST_XrSyncActions(xr_context, active_action_set ? active_action_set->name : NULL); if (!ret) { return; @@ -1108,7 +1224,7 @@ void wm_xr_session_actions_update(wmWindowManager *wm) wmWindow *win = wm_xr_session_root_window_or_fallback_get(wm, xr->runtime); if (active_action_set->controller_grip_action && active_action_set->controller_aim_action) { - wm_xr_session_controller_data_update(&xr->session_settings, + wm_xr_session_controller_data_update(settings, active_action_set->controller_grip_action, active_action_set->controller_aim_action, xr_context, diff --git a/source/blender/windowmanager/xr/wm_xr.h b/source/blender/windowmanager/xr/wm_xr.h index 0f0fbe8bc00..caba6038c56 100644 --- a/source/blender/windowmanager/xr/wm_xr.h +++ b/source/blender/windowmanager/xr/wm_xr.h @@ -30,3 +30,6 @@ 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); + +/* wm_xr_operators.c */ +void wm_xr_operatortypes_register(void); |