This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/windowmanager/xr/intern/wm_xr_operators.c

1525 lines
47 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \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);
/* Lazily-create XR context - tries to dynamic-link 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;
}
/* 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). */
switch (event->val) {
case KM_PRESS:
return OPERATOR_RUNNING_MODAL;
case KM_RELEASE:
wm_xr_grab_uninit(op);
return OPERATOR_FINISHED;
default:
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_target_select = (selectable_only ?
SCE_SNAP_TARGET_ONLY_SELECTABLE :
SCE_SNAP_TARGET_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(ELEM(mode, XR_FLY_TURNLEFT, 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 = ELEM(mode, XR_FLY_TURNLEFT, 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 initial 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);
switch (event->val) {
case KM_PRESS:
return OPERATOR_RUNNING_MODAL;
case 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;
}
default:
/* 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);
}
/** \} */