OpenXR: VR Advanced Locomotion Phase 1

Includes the following changes to the existing Locomotion system for VR Scene Inspection:
 * new VR Navigation Preferences and VR Session Settings
 * changes to XR raycast logic and its visualization
 * new XR vignette that appears when moving
 * snap turning

Pull Request: https://projects.blender.org/blender/blender/pulls/144241
This commit is contained in:
hogan.mastanduno
2025-10-01 22:16:12 +02:00
committed by Julian Eisel
parent 1df4a09539
commit dde9d21b91
36 changed files with 1146 additions and 169 deletions

View File

@@ -208,6 +208,14 @@ const UserDef U_default = {
.flag = 0,
},
.xr_navigation =
{
.vignette_intensity = 60.0f,
.turn_amount = DEG2RAD(30),
.turn_speed = DEG2RAD(60),
.flag = USER_XR_NAV_SNAP_TURN,
},
.space_data =
{
.section_active = USER_SECTION_INTERFACE,
@@ -246,4 +254,12 @@ const UserDef U_default = {
{
.is_dirty = 0,
},
.xr_navigation =
{
.vignette_intensity = 60.0f,
.turn_amount = DEG2RAD(30),
.turn_speed = DEG2RAD(60),
.flag = USER_XR_NAV_SNAP_TURN,
},
};

View File

@@ -24,8 +24,9 @@ if "bpy" in locals():
importlib.reload(gui)
importlib.reload(operators)
importlib.reload(properties)
importlib.reload(preferences)
else:
from . import action_map, gui, operators, properties
from . import action_map, gui, operators, properties, preferences
import bpy
@@ -39,6 +40,7 @@ def register():
gui.register()
operators.register()
properties.register()
preferences.register()
def unregister():
@@ -50,3 +52,4 @@ def unregister():
gui.unregister()
operators.unregister()
properties.unregister()
preferences.unregister()

View File

@@ -37,7 +37,8 @@ actionconfig_data = \
("teleport", {"type": 'FLOAT', "user_paths": ['/user/hand/left', '/user/hand/right'], "op": 'wm.xr_navigation_teleport', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("interpolation", 0.9),
("color", (0.0, 1.0, 1.0, 1.0)),
("hit_color", (0.0, 1.0, 1.0, 1.0)),
("miss_color", (1.0, 0.0, 0.0, 1.0)),
],
},
{"bindings":
@@ -74,6 +75,7 @@ actionconfig_data = \
("fly_forward", {"type": 'FLOAT', "user_paths": ['/user/hand/left'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'VIEWER_FORWARD'),
("alt_mode", 'UP'),
("lock_location_z", True),
],
},
@@ -92,6 +94,7 @@ actionconfig_data = \
("fly_back", {"type": 'FLOAT', "user_paths": ['/user/hand/left'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'VIEWER_BACK'),
("alt_mode", 'DOWN'),
("lock_location_z", True),
],
},
@@ -110,6 +113,7 @@ actionconfig_data = \
("fly_left", {"type": 'FLOAT', "user_paths": ['/user/hand/left'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'VIEWER_LEFT'),
("alt_mode", 'TURNLEFT'),
("lock_location_z", True),
],
},
@@ -128,6 +132,7 @@ actionconfig_data = \
("fly_right", {"type": 'FLOAT', "user_paths": ['/user/hand/left'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'VIEWER_RIGHT'),
("alt_mode", 'TURNRIGHT'),
("lock_location_z", True),
],
},
@@ -146,8 +151,8 @@ actionconfig_data = \
("fly_up", {"type": 'FLOAT', "user_paths": ['/user/hand/right'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'UP'),
("speed_min", 0.014),
("speed_max", 0.042),
("alt_mode", 'VIEWER_FORWARD'),
("alt_lock_location_z", True),
],
},
{"bindings":
@@ -165,8 +170,8 @@ actionconfig_data = \
("fly_down", {"type": 'FLOAT', "user_paths": ['/user/hand/right'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'DOWN'),
("speed_min", 0.014),
("speed_max", 0.042),
("alt_mode", 'VIEWER_BACK'),
("alt_lock_location_z", True),
],
},
{"bindings":
@@ -184,8 +189,8 @@ actionconfig_data = \
("fly_turnleft", {"type": 'FLOAT', "user_paths": ['/user/hand/right'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'TURNLEFT'),
("speed_min", 0.01),
("speed_max", 0.03),
("alt_mode", 'VIEWER_LEFT'),
("alt_lock_location_z", True),
],
},
{"bindings":
@@ -203,8 +208,8 @@ actionconfig_data = \
("fly_turnright", {"type": 'FLOAT', "user_paths": ['/user/hand/right'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'TURNRIGHT'),
("speed_min", 0.01),
("speed_max", 0.03),
("alt_mode", 'VIEWER_RIGHT'),
("alt_lock_location_z", True),
],
},
{"bindings":
@@ -261,7 +266,8 @@ actionconfig_data = \
{"op_properties":
[("interpolation", 0.9),
("from_viewer", True),
("color", (0.0, 1.0, 1.0, 1.0)),
("hit_color", (0.0, 1.0, 1.0, 1.0)),
("miss_color", (1.0, 0.0, 0.0, 1.0)),
],
},
{"bindings":
@@ -278,6 +284,7 @@ actionconfig_data = \
("fly_forward", {"type": 'FLOAT', "user_paths": ['/user/gamepad'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'VIEWER_FORWARD'),
("alt_mode", 'UP'),
("lock_location_z", True),
],
},
@@ -289,6 +296,7 @@ actionconfig_data = \
("fly_back", {"type": 'FLOAT', "user_paths": ['/user/gamepad'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'VIEWER_BACK'),
("alt_mode", 'DOWN'),
("lock_location_z", True),
],
},
@@ -300,6 +308,7 @@ actionconfig_data = \
("fly_left", {"type": 'FLOAT', "user_paths": ['/user/gamepad'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'VIEWER_LEFT'),
("alt_mode", 'TURNLEFT'),
("lock_location_z", True),
],
},
@@ -311,6 +320,7 @@ actionconfig_data = \
("fly_right", {"type": 'FLOAT', "user_paths": ['/user/gamepad'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'VIEWER_RIGHT'),
("alt_mode", 'TURNRIGHT'),
("lock_location_z", True),
],
},
@@ -322,9 +332,9 @@ actionconfig_data = \
("fly_up", {"type": 'FLOAT', "user_paths": ['/user/gamepad'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'UP'),
("speed_min", 0.014),
("speed_max", 0.042),
],
("alt_mode", 'VIEWER_FORWARD'),
("alt_lock_location_z", True),
]
},
{"bindings":
[("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_paths": ['/input/thumbstick_right/y'], "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),
@@ -334,9 +344,9 @@ actionconfig_data = \
("fly_down", {"type": 'FLOAT', "user_paths": ['/user/gamepad'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'DOWN'),
("speed_min", 0.014),
("speed_max", 0.042),
],
("alt_mode", 'VIEWER_BACK'),
("alt_lock_location_z", True),
]
},
{"bindings":
[("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_paths": ['/input/thumbstick_right/y'], "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
@@ -346,9 +356,9 @@ actionconfig_data = \
("fly_turnleft", {"type": 'FLOAT', "user_paths": ['/user/gamepad'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'TURNLEFT'),
("speed_min", 0.01),
("speed_max", 0.03),
],
("alt_mode", 'VIEWER_LEFT'),
("alt_lock_location_z", True),
]
},
{"bindings":
[("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_paths": ['/input/thumbstick_right/x'], "threshold": '0.30000001192092896', "axis_region": 'NEGATIVE'}),
@@ -358,9 +368,9 @@ actionconfig_data = \
("fly_turnright", {"type": 'FLOAT', "user_paths": ['/user/gamepad'], "op": 'wm.xr_navigation_fly', "op_mode": 'MODAL', "bimanual": 'False', "haptic_name": '', "haptic_match_user_paths": 'False', "haptic_duration": '0.0', "haptic_frequency": '0.0', "haptic_amplitude": '0.0', "haptic_mode": 'PRESS'},
{"op_properties":
[("mode", 'TURNRIGHT'),
("speed_min", 0.01),
("speed_max", 0.03),
],
("alt_mode", 'VIEWER_RIGHT'),
("alt_lock_location_z", True),
]
},
{"bindings":
[("gamepad", {"profile": '/interaction_profiles/microsoft/xbox_controller', "component_paths": ['/input/thumbstick_right/x'], "threshold": '0.30000001192092896', "axis_region": 'POSITIVE'}),

View File

@@ -37,6 +37,7 @@ class VRDefaultActions(Enum):
FLY_TURNLEFT = "fly_turnleft"
FLY_TURNRIGHT = "fly_turnright"
NAV_RESET = "nav_reset"
SWAP_HANDS = "swap_hands"
HAPTIC = "haptic"
HAPTIC_LEFT = "haptic_left"
HAPTIC_RIGHT = "haptic_right"
@@ -1146,6 +1147,91 @@ def vr_defaults_create_default(session_state):
'ANY',
'ANY')
ami = vr_defaults_action_add(am,
VRDefaultActions.SWAP_HANDS.value,
["/user/hand/left",
"/user/hand/right"],
"wm.xr_navigation_swap_hands",
'PRESS',
False,
"haptic",
True,
0.3,
3000.0,
0.5,
'PRESS')
if ami:
"""
vr_defaults_actionbinding_add(ami,
VRDefaultActionbindings.HUAWEI.value,
VRDefaultActionprofiles.HUAWEI.value,
["/input/back/click",
"/input/back/click"],
0.3,
'ANY',
'ANY')
"""
vr_defaults_actionbinding_add(ami,
VRDefaultActionbindings.INDEX.value,
VRDefaultActionprofiles.INDEX.value,
["/input/b/click",
"/input/b/click"],
0.3,
'ANY',
'ANY')
vr_defaults_actionbinding_add(ami,
VRDefaultActionbindings.OCULUS.value,
VRDefaultActionprofiles.OCULUS.value,
["/input/y/click",
"/input/b/click"],
0.3,
'ANY',
'ANY')
vr_defaults_actionbinding_add(ami,
VRDefaultActionbindings.REVERB_G2.value,
VRDefaultActionprofiles.REVERB_G2.value,
["/input/y/click",
"/input/b/click"],
0.3,
'ANY',
'ANY')
"""
vr_defaults_actionbinding_add(ami,
VRDefaultActionbindings.VIVE.value,
VRDefaultActionprofiles.VIVE.value,
["/input/menu/click",
"/input/menu/click"],
0.3,
'ANY',
'ANY')
"""
vr_defaults_actionbinding_add(ami,
VRDefaultActionbindings.VIVE_COSMOS.value,
VRDefaultActionprofiles.VIVE_COSMOS.value,
["/input/y/click",
"/input/b/click"],
0.3,
'ANY',
'ANY')
vr_defaults_actionbinding_add(ami,
VRDefaultActionbindings.VIVE_FOCUS.value,
VRDefaultActionprofiles.VIVE_FOCUS.value,
["/input/y/click",
"/input/b/click"],
0.3,
'ANY',
'ANY')
"""
vr_defaults_actionbinding_add(ami,
VRDefaultActionbindings.WMR.value,
VRDefaultActionprofiles.WMR.value,
["/input/menu/click",
"/input/menu/click"],
0.3,
'ANY',
'ANY')
"""
ami = vr_defaults_haptic_action_add(am,
VRDefaultActions.HAPTIC.value,
["/user/hand/left",

View File

@@ -97,7 +97,9 @@ class VIEW3D_PT_vr_session_view(Panel):
col = layout.column(align=True)
col.prop(session_settings, "clip_start", text="Clip Start")
col.prop(session_settings, "clip_end", text="End", text_ctxt=i18n_contexts.id_camera)
col = layout.column(align=True)
col.prop(session_settings, "fly_speed", text="Fly Speed")
class VIEW3D_PT_vr_session_view_object_type_visibility(VIEW3D_PT_object_type_visibility):
def draw(self, context):

View File

@@ -0,0 +1,67 @@
# SPDX-FileCopyrightText: 2023 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.types import (
Panel,
)
class USERPREF_PT_vr_navigation(Panel):
bl_space_type = 'PREFERENCES'
bl_region_type = 'WINDOW'
bl_context = "navigation"
bl_label = "VR Navigation"
def draw(self, context):
layout = self.layout
width = context.region.width
ui_scale = context.preferences.system.ui_scale
# No horizontal margin if region is rather small.
is_wide = width > (350 * ui_scale)
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
row = layout.row()
if is_wide:
row.label() # Needed so col below is centered.
col = row.column()
col.ui_units_x = 50
# Implemented by sub-classes.
self.draw_centered(context, col)
if is_wide:
row.label() # Needed so col above is centered.
def draw_centered(self, context, layout):
prefs = context.preferences
nav = prefs.inputs.xr_navigation
col = layout.column()
col.row().prop(nav, "vignette_intensity", text="Vignette Intensity")
if nav.snap_turn:
col.row().prop(nav, "turn_amount", text="Turn Amount")
else:
col.row().prop(nav, "turn_speed", text="Turn Speed")
col.row().prop(nav, "snap_turn", text="Snap Turn")
col.row().prop(nav, "invert_rotation", text="Invert Rotation")
classes = (
USERPREF_PT_vr_navigation,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)

View File

@@ -27,7 +27,7 @@
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 98
#define BLENDER_FILE_SUBVERSION 99
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@@ -3772,6 +3772,12 @@ void blo_do_versions_500(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 99)) {
LISTBASE_FOREACH (wmWindowManager *, wm, &bmain->wm) {
wm->xr.session_settings.fly_speed = 3.0f;
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@@ -381,6 +381,11 @@ static void blo_update_defaults_paint(Paint *paint)
}
}
static void blo_update_defaults_windowmanager(wmWindowManager *wm)
{
wm->xr.session_settings.fly_speed = 3.0f;
}
static void blo_update_defaults_scene(Main *bmain, Scene *scene)
{
ToolSettings *ts = scene->toolsettings;
@@ -618,6 +623,8 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
/* Work-spaces. */
LISTBASE_FOREACH (wmWindowManager *, wm, &bmain->wm) {
blo_update_defaults_windowmanager(wm);
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
LISTBASE_FOREACH (WorkSpace *, workspace, &bmain->workspaces) {
WorkSpaceLayout *layout = BKE_workspace_active_layout_for_workspace_get(

View File

@@ -1691,6 +1691,13 @@ void blo_do_versions_userdef(UserDef *userdef)
}
}
if (!USER_VERSION_ATLEAST(500, 99)) {
userdef->xr_navigation.vignette_intensity = 50.0f;
userdef->xr_navigation.turn_amount = DEG2RAD(30);
userdef->xr_navigation.turn_speed = DEG2RAD(60);
userdef->xr_navigation.flag = USER_XR_NAV_SNAP_TURN;
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a USER_VERSION_ATLEAST check.

View File

@@ -22,6 +22,7 @@ namespace blender::draw::overlay {
class Background : Overlay {
private:
PassSimple bg_ps_ = {"Background"};
PassSimple bg_vignette_ps_ = {"Background Vignette"};
gpu::FrameBuffer *framebuffer_ref_ = nullptr;
@@ -31,6 +32,7 @@ class Background : Overlay {
DRWState pass_state = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_BACKGROUND;
float4 color_override(0.0f, 0.0f, 0.0f, 0.0f);
int background_type;
const float vignette_aperture = state.v3d->vignette_aperture, vignette_falloff = 0.15f;
if (state.is_viewport_image_render && !state.draw_background) {
background_type = BG_SOLID;
@@ -96,7 +98,26 @@ class Background : Overlay {
bg_ps_.bind_texture("depth_buffer", &res.depth_tx);
bg_ps_.push_constant("color_override", color_override);
bg_ps_.push_constant("bg_type", background_type);
bg_ps_.push_constant("vignette_enabled", false);
bg_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3);
if (state.vignette_enabled) {
bg_vignette_ps_.init();
bg_vignette_ps_.framebuffer_set(&framebuffer_ref_);
bg_vignette_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ALPHA);
bg_vignette_ps_.shader_set(res.shaders->background_fill.get());
bg_vignette_ps_.bind_ubo(OVERLAY_GLOBALS_SLOT, &res.globals_buf);
bg_vignette_ps_.bind_ubo(DRW_CLIPPING_UBO_SLOT, &res.clip_planes_buf);
bg_vignette_ps_.bind_texture("color_buffer", &res.color_render_tx);
bg_vignette_ps_.bind_texture("depth_buffer", &res.depth_tx);
bg_vignette_ps_.push_constant("color_override", color_override);
bg_vignette_ps_.push_constant("bg_type", background_type);
bg_vignette_ps_.push_constant("vignette_enabled", true);
bg_vignette_ps_.push_constant("vignette_aperture", vignette_aperture);
bg_vignette_ps_.push_constant("vignette_falloff", vignette_falloff);
bg_vignette_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
}
void draw_output(Framebuffer &framebuffer, Manager &manager, View &view) final
@@ -104,6 +125,12 @@ class Background : Overlay {
framebuffer_ref_ = framebuffer;
manager.submit(bg_ps_, view);
}
void draw_vignette(Framebuffer &framebuffer, Manager &manager, View &view)
{
framebuffer_ref_ = framebuffer;
manager.submit(bg_vignette_ps_, view);
}
};
} // namespace blender::draw::overlay

View File

@@ -66,6 +66,8 @@ void Instance::init()
state.xray_opacity = state.xray_enabled ? XRAY_ALPHA(state.v3d) : 1.0f;
state.xray_flag_enabled = SHADING_XRAY_FLAG_ENABLED(state.v3d->shading) &&
!state.is_depth_only_drawing;
state.vignette_enabled = ctx->mode == DRWContext::VIEWPORT_XR &&
state.v3d->vignette_aperture < M_SQRT1_2;
const bool viewport_uses_workbench = state.v3d->shading.type <= OB_SOLID ||
BKE_scene_uses_blender_workbench(state.scene);
@@ -962,6 +964,10 @@ void Instance::draw_v3d(Manager &manager, View &view)
cursor.draw_output(resources.overlay_output_color_only_fb, manager, view);
draw_text(resources.overlay_output_color_only_fb);
if (state.vignette_enabled) {
background.draw_vignette(resources.overlay_output_color_only_fb, manager, view);
}
}
}

View File

@@ -153,6 +153,8 @@ struct State {
bool draw_background = false;
/** True if the render engine outputs satisfactory depth information to the depth buffer. */
bool is_render_depth_available = false;
/** Whether we should render a vignette over the scene. */
bool vignette_enabled = false;
/** Should text draw in this mode? */
bool show_text = false;
bool hide_overlays = false;

View File

@@ -22,6 +22,9 @@ SAMPLER(0, sampler2D, color_buffer)
SAMPLER(1, sampler2DDepth, depth_buffer)
PUSH_CONSTANT(int, bg_type)
PUSH_CONSTANT(float4, color_override)
PUSH_CONSTANT(float, vignette_aperture)
PUSH_CONSTANT(float, vignette_falloff)
PUSH_CONSTANT(bool, vignette_enabled)
FRAGMENT_SOURCE("overlay_background_frag.glsl")
FRAGMENT_OUT(0, float4, frag_color)
ADDITIONAL_INFO(gpu_fullscreen)

View File

@@ -31,8 +31,19 @@ void main()
* This removes the alpha channel and put the background behind reference images
* while masking the reference images by the render alpha.
*/
float alpha = texture(color_buffer, screen_uv).a;
float depth = texture(depth_buffer, screen_uv).r;
float alpha;
float depth;
if (vignette_enabled) {
const float dist = length(screen_uv - 0.5f);
alpha = smoothstep(vignette_aperture, vignette_aperture + vignette_falloff, dist);
depth = 0.0f;
}
else {
alpha = texture(color_buffer, screen_uv).a;
depth = texture(depth_buffer, screen_uv).r;
}
float3 bg_col;
float3 col_high;
@@ -90,11 +101,16 @@ void main()
bg_col = mix(bg_col, color_override.rgb, color_override.a);
/* Mimic alpha under behavior. Result is premultiplied. */
frag_color = float4(bg_col, 1.0f) * (1.0f - alpha);
if (vignette_enabled) {
frag_color = float4(bg_col, alpha);
}
else {
/* Mimic alpha under behavior. Result is premultiplied. */
frag_color = float4(bg_col, 1.0f) * (1.0f - alpha);
/* Special case: If the render is not transparent, do not clear alpha values. */
if (depth == 1.0f && alpha == 1.0f) {
frag_color.a = 1.0f;
/* Special case: If the render is not transparent, do not clear alpha values. */
if (depth == 1.0f && alpha == 1.0f) {
frag_color.a = 1.0f;
}
}
}

View File

@@ -55,6 +55,7 @@ void ED_view3d_draw_offscreen_simple(Depsgraph *depsgraph,
const float winmat[4][4],
float clip_start,
float clip_end,
float vignette_aperture,
bool is_xr_surface,
bool is_image_render,
bool draw_background,

View File

@@ -1884,6 +1884,7 @@ void ED_view3d_draw_offscreen_simple(Depsgraph *depsgraph,
const float winmat[4][4],
float clip_start,
float clip_end,
float vignette_aperture,
bool is_xr_surface,
bool is_image_render,
bool draw_background,
@@ -1965,6 +1966,7 @@ void ED_view3d_draw_offscreen_simple(Depsgraph *depsgraph,
v3d.clip_end = clip_end;
/* Actually not used since we pass in the projection matrix. */
v3d.lens = 0;
v3d.vignette_aperture = vignette_aperture;
/* WORKAROUND: Disable overscan because it is not supported for arbitrary input matrices.
* The proper fix to this would be to support arbitrary matrices in `eevee::Camera::sync()`. */

View File

@@ -486,6 +486,7 @@ set(GLSL_SRC
shaders/infos/gpu_shader_sequencer_infos.hh
shaders/infos/gpu_shader_simple_lighting_infos.hh
shaders/infos/gpu_shader_text_infos.hh
shaders/infos/gpu_shader_xr_raycast_infos.hh
shaders/infos/gpu_srgb_to_framebuffer_space_infos.hh
shaders/gpu_shader_depth_only_frag.glsl
@@ -540,6 +541,7 @@ set(GLSL_SRC
shaders/gpu_shader_text_vert.glsl
shaders/gpu_shader_text_frag.glsl
shaders/gpu_shader_xr_raycast_vert.glsl
shaders/gpu_shader_keyframe_shape_vert.glsl
shaders/gpu_shader_keyframe_shape_frag.glsl

View File

@@ -91,6 +91,9 @@ enum GPUBuiltinShader {
/** Draw sequencer zebra pattern (overexposed regions). */
GPU_SHADER_SEQUENCER_ZEBRA,
/** Draw xr raycast as a ruled spline surface. */
GPU_SHADER_XR_RAYCAST,
/** Compute shaders to generate 2d index buffers (mainly for curve drawing). */
GPU_SHADER_INDEXBUF_POINTS,
GPU_SHADER_INDEXBUF_LINES,

View File

@@ -0,0 +1,11 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#define XR_MAX_RAYCASTS 8

View File

@@ -119,6 +119,8 @@ static const char *builtin_shader_create_info_name(GPUBuiltinShader shader)
return "gpu_shader_index_2d_array_lines";
case GPU_SHADER_INDEXBUF_TRIS:
return "gpu_shader_index_2d_array_tris";
case GPU_SHADER_XR_RAYCAST:
return "gpu_shader_xr_raycast";
default:
BLI_assert_unreachable();
return "";

View File

@@ -0,0 +1,43 @@
/* SPDX-FileCopyrightText: 2016-2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "infos/gpu_shader_xr_raycast_infos.hh"
VERTEX_SHADER_CREATE_INFO(gpu_shader_xr_raycast)
vec3 catmull_rom(vec3 p0, vec3 p1, vec3 p2, vec3 p3, float t)
{
float t2 = t * t;
float t3 = t2 * t;
return 0.5 * ((2.0 * p1) + (-p0 + p2) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 +
(-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3);
}
vec3 get_control_point(int idx)
{
idx = clamp(idx, 0, control_point_count - 1);
return control_points[idx].xyz;
}
void main()
{
int sample_idx = gl_VertexID >> 1;
float side = ((gl_VertexID & 1) != 0) ? -1.0 : 1.0;
/** Interpolate within the range: [0, segment_count] */
float sample_value = float(sample_idx) * float(control_point_count - 1) /
float(sample_count - 1);
int segment_idx = int(sample_value);
float t = sample_value - float(segment_idx);
vec3 p0 = get_control_point(segment_idx - 1);
vec3 p1 = get_control_point(segment_idx + 0);
vec3 p2 = get_control_point(segment_idx + 1);
vec3 p3 = get_control_point(segment_idx + 2);
vec3 pos = catmull_rom(p0, p1, p2, p3, t) + 0.5 * width * side * right_vector;
gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
}

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#ifdef GPU_SHADER
# pragma once
# include "gpu_glsl_cpp_stubs.hh"
# include "GPU_shader_shared.hh"
#endif
#include "GPU_xr_defines.hh"
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(gpu_shader_xr_raycast)
DEFINE_VALUE("XR_MAX_RAYCASTS", STRINGIFY(XR_MAX_RAYCASTS))
FRAGMENT_OUT(0, float4, fragColor)
PUSH_CONSTANT_ARRAY(float4, control_points, XR_MAX_RAYCASTS + 1)
PUSH_CONSTANT(float4x4, ModelViewProjectionMatrix)
PUSH_CONSTANT(float4, color)
PUSH_CONSTANT(float3, right_vector)
PUSH_CONSTANT(float, width)
PUSH_CONSTANT(int, control_point_count)
PUSH_CONSTANT(int, sample_count)
VERTEX_SOURCE("gpu_shader_xr_raycast_vert.glsl")
FRAGMENT_SOURCE("gpu_shader_uniform_color_frag.glsl")
ADDITIONAL_INFO(gpu_srgb_to_framebuffer_space)
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()

View File

@@ -167,6 +167,14 @@ typedef struct WalkNavigation {
char _pad0[6];
} WalkNavigation;
typedef struct XrNavigation {
float vignette_intensity;
float turn_speed;
float turn_amount;
short flag;
char _pad0[2];
} XrNavigation;
typedef struct UserDef_Runtime {
/** Mark as changed so the preferences are saved on exit. */
char is_dirty;
@@ -619,6 +627,7 @@ typedef struct UserDef {
char statusbar_flag; /* eUserpref_StatusBar_Flag */
struct WalkNavigation walk_navigation;
struct XrNavigation xr_navigation;
/** The UI for the user preferences. */
UserDef_SpaceData space_data;
@@ -1108,6 +1117,12 @@ typedef enum eUserpref_FactorDisplay {
USER_FACTOR_AS_PERCENTAGE = 1,
} eUserpref_FactorDisplay;
/** #UserDef.xr_navigation_flag */
typedef enum eUserpref_XrNavigationFlags {
USER_XR_NAV_SNAP_TURN = (1 << 0),
USER_XR_NAV_INVERT_ROTATION = (1 << 1),
} eUserpref_XrNavigationFlags;
typedef enum eUserpref_RenderDisplayType {
USER_RENDER_DISPLAY_NONE = 0,
USER_RENDER_DISPLAY_SCREEN = 1,

View File

@@ -350,7 +350,8 @@ typedef struct View3D {
float lens, grid;
float clip_start, clip_end;
float ofs[3] DNA_DEPRECATED;
float vignette_aperture;
float ofs[2] DNA_DEPRECATED;
char _pad[1];

View File

@@ -38,6 +38,10 @@ typedef struct XrSessionSettings {
/** Object type settings to apply to VR view (unlike shading, not shared with window 3D-View). */
int object_type_exclude_viewport;
int object_type_exclude_select;
/** Fly speed. */
float fly_speed;
float padding;
} XrSessionSettings;
typedef enum eXrSessionFlag {

View File

@@ -531,6 +531,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
SDNA_DEFAULT_DECL_EX(UserDef_SpaceData, UserDef.space_data),
SDNA_DEFAULT_DECL_EX(UserDef_FileSpaceData, UserDef.file_space_data),
SDNA_DEFAULT_DECL_EX(WalkNavigation, UserDef.walk_navigation),
SDNA_DEFAULT_DECL_EX(XrNavigation, UserDef.xr_navigation),
SDNA_DEFAULT_DECL(bUserAssetLibrary),
SDNA_DEFAULT_DECL(bUserExtensionRepo),
SDNA_DEFAULT_DECL(bUserAssetShelfSettings),

View File

@@ -4754,6 +4754,43 @@ static void rna_def_userdef_walk_navigation(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Reverse Mouse", "Reverse the vertical movement of the mouse");
}
static void rna_def_userdef_xr_navigation(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "XrNavigation", nullptr);
RNA_def_struct_sdna(srna, "XrNavigation");
RNA_def_struct_ui_text(srna, "VR Navigation", "VR navigation settings");
prop = RNA_def_property(srna, "vignette_intensity", PROP_FLOAT, PROP_PERCENTAGE);
RNA_def_property_range(prop, 0, 100.0);
RNA_def_property_ui_range(prop, 0, 100.0, 1000, 0);
RNA_def_property_ui_text(
prop, "Vignette Intensity", "Intensity of vignette that appears when moving");
prop = RNA_def_property(srna, "turn_speed", PROP_FLOAT, PROP_ANGLE);
RNA_def_property_range(prop, 0, FLT_MAX);
RNA_def_property_ui_range(prop, 0, FLT_MAX, 1000, 0);
RNA_def_property_ui_text(prop, "Turn Speed", "Turn speed in degrees per second");
prop = RNA_def_property(srna, "turn_amount", PROP_FLOAT, PROP_ANGLE);
RNA_def_property_range(prop, 0, DEG2RAD(360));
RNA_def_property_ui_range(prop, 0, DEG2RAD(360), 1000, 0);
RNA_def_property_ui_text(prop, "Turn Amount", "Amount in degrees per turn when using snap turn");
prop = RNA_def_property(srna, "snap_turn", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", USER_XR_NAV_SNAP_TURN);
RNA_def_property_ui_text(
prop,
"Snap Turn",
"Instantly rotates the camera by a fixed angle instead of smoothly turning");
prop = RNA_def_property(srna, "invert_rotation", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", USER_XR_NAV_INVERT_ROTATION);
RNA_def_property_ui_text(prop, "Invert Rotation", "Reverses the direction of rotation input");
}
static void rna_def_userdef_view(BlenderRNA *brna)
{
static const EnumPropertyItem timecode_styles[] = {
@@ -6493,6 +6530,12 @@ static void rna_def_userdef_input(BlenderRNA *brna)
"restarting Blender for changes to take effect)");
RNA_def_property_update(prop, 0, "rna_userdef_input_devices");
prop = RNA_def_property(srna, "xr_navigation", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, nullptr, "xr_navigation");
RNA_def_property_flag(prop, PROP_NEVER_NULL);
RNA_def_property_struct_type(prop, "XrNavigation");
RNA_def_property_ui_text(prop, "XR Navigation", "Settings for navigation in XR");
# ifdef WITH_INPUT_NDOF
/* 3D mouse settings */
/* global options */
@@ -7439,6 +7482,7 @@ void RNA_def_userdef(BlenderRNA *brna)
rna_def_userdef_dothemes(brna);
rna_def_userdef_solidlight(brna);
rna_def_userdef_walk_navigation(brna);
rna_def_userdef_xr_navigation(brna);
srna = RNA_def_struct(brna, "Preferences", nullptr);
RNA_def_struct_sdna(srna, "UserDef");

View File

@@ -2035,16 +2035,22 @@ static void rna_def_xr_session_settings(BlenderRNA *brna)
prop = RNA_def_property(srna, "clip_start", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_range(prop, 1e-6f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.001f, FLT_MAX, 10, 3);
RNA_def_property_ui_range(prop, 0.001f, FLT_MAX, 0.1 * 100, 3);
RNA_def_property_ui_text(prop, "Clip Start", "VR viewport near clipping distance");
RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, nullptr);
prop = RNA_def_property(srna, "clip_end", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_range(prop, 1e-6f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.001f, FLT_MAX, 10, 3);
RNA_def_property_ui_range(prop, 0.001f, FLT_MAX, 10 * 100, 3);
RNA_def_property_ui_text(prop, "Clip End", "VR viewport far clipping distance");
RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, nullptr);
prop = RNA_def_property(srna, "fly_speed", PROP_FLOAT, PROP_NONE);
RNA_def_property_range(prop, 1e-6f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.001f, FLT_MAX, 0.5 * 100, 3);
RNA_def_property_ui_text(prop, "Fly Speed", "Fly speed in meters per second");
RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, nullptr);
prop = RNA_def_property(srna, "use_positional_tracking", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(prop,
"rna_XrSessionSettings_use_positional_tracking_get",

View File

@@ -8,6 +8,7 @@ set(INC
gizmo/intern
../compositor
../editors/include
../gpu
../makesrna
../../../intern/memutil

View File

@@ -2180,6 +2180,9 @@ 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(wmXrSessionState *state);
void WM_xr_session_state_vignette_reset(wmXrSessionState *state);
void WM_xr_session_state_vignette_activate(wmXrData *xr);
void WM_xr_session_state_vignette_update(wmXrSessionState *state);
ARegionType *WM_xr_surface_controller_region_type_get();

View File

@@ -184,6 +184,8 @@ void wm_xr_runtime_data_free(wmXrRuntimeData **runtime)
if ((*runtime)->area) {
wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
wmWindow *win = wm_xr_session_root_window_or_fallback_get(wm, (*runtime));
WM_event_remove_handlers_by_area(&win->handlers, (*runtime)->area);
ED_area_offscreen_free(wm, win, (*runtime)->area);
(*runtime)->area = nullptr;
}

View File

@@ -30,6 +30,8 @@
#include "GPU_state.hh"
#include "GPU_viewport.hh"
#include "UI_resources.hh"
#include "WM_api.hh"
#include "wm_xr_intern.hh"
@@ -180,6 +182,7 @@ void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata)
winmat,
settings->clip_start,
settings->clip_end,
session_state->vignette_data->aperture,
true,
false,
true,
@@ -337,7 +340,7 @@ static void wm_xr_controller_aim_draw(const XrSessionSettings *settings, wmXrSes
break;
case XR_CONTROLLER_DRAW_DARK_RAY:
case XR_CONTROLLER_DRAW_LIGHT_RAY:
draw_ray = true;
draw_ray = !state->is_raycast_shown;
break;
}

View File

@@ -53,6 +53,8 @@ struct wmXrSessionState {
bool force_reset_to_base_pose;
bool is_view_data_set;
bool swap_hands;
bool is_raycast_shown;
/** Current navigation transforms. */
GHOST_XrPose nav_pose;
@@ -72,6 +74,12 @@ struct wmXrSessionState {
struct wmXrActionSet *active_action_set;
/* Name of the action set (if any) to activate before the next actions sync. */
char active_action_set_next[64]; /* #MAX_NAME. */
/** The current state and parameters of the vignette that appears while moving. */
struct wmXrVignetteData *vignette_data;
/** Model used to draw teleportation raycast. */
blender::gpu::Batch *raycast_model;
};
struct wmXrRuntimeData {
@@ -200,6 +208,22 @@ struct wmXrActionSet {
ListBase active_haptic_actions;
};
struct wmXrVignetteData {
/** Vignette state. */
float aperture;
float aperture_velocity;
/** Vignette parameters. */
float initial_aperture;
float initial_aperture_velocity;
float aperture_min;
float aperture_max;
float aperture_velocity_max;
float aperture_velocity_delta;
};
/* `wm_xr.cc` */
wmXrRuntimeData *wm_xr_runtime_data_create();

View File

@@ -37,6 +37,10 @@
#include "GPU_immediate.hh"
#include "GPU_state.hh"
#include "GPU_batch_presets.hh"
#include "GPU_matrix.hh"
#include "GPU_xr_defines.hh"
#include "MEM_guardedalloc.h"
#include "RNA_access.hh"
@@ -529,6 +533,11 @@ static void wm_xr_navigation_grab_bimanual_state_update(const wmXrActionData *ac
}
}
static void wm_xr_navigation_grab_cancel(bContext * /*C*/, wmOperator *op)
{
wm_xr_grab_uninit(op);
}
static wmOperatorStatus wm_xr_navigation_grab_modal(bContext *C,
wmOperator *op,
const wmEvent *event)
@@ -542,6 +551,8 @@ static wmOperatorStatus wm_xr_navigation_grab_modal(bContext *C,
wmWindowManager *wm = CTX_wm_manager(C);
wmXrData *xr = &wm->xr;
WM_xr_session_state_vignette_activate(xr);
const bool do_bimanual = wm_xr_navigation_grab_can_do_bimanual(actiondata, data);
data->loc_lock = RNA_boolean_get(op->ptr, "lock_location");
@@ -588,6 +599,7 @@ static void WM_OT_xr_navigation_grab(wmOperatorType *ot)
/* Callbacks. */
ot->invoke = wm_xr_navigation_grab_invoke;
ot->exec = wm_xr_navigation_grab_exec;
ot->cancel = wm_xr_navigation_grab_cancel;
ot->modal = wm_xr_navigation_grab_modal;
ot->poll = wm_xr_operator_sessionactive;
@@ -613,21 +625,60 @@ static void WM_OT_xr_navigation_grab(wmOperatorType *ot)
* \{ */
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};
static const float g_xr_default_raycast_hit_color[4] = {0.35f, 0.35f, 1.0f, 1.0f};
static const float g_xr_default_raycast_miss_color[4] = {1.0f, 0.35f, 0.35f, 1.0f};
static const float g_xr_default_raycast_fallback_color[4] = {0.35f, 0.35f, 1.0f, 1.0f};
enum XrRaycastResult : uint8_t {
XR_RAYCAST_MISS,
XR_RAYCAST_HIT,
XR_RAYCAST_FALLBACK,
};
struct XrRaycastData {
/** Raycast info */
bool from_viewer;
float origin[3];
/** Raycast results */
XrRaycastResult result;
int num_points;
float points[XR_MAX_RAYCASTS + 1][4];
float direction[3];
float end[3];
/** Raycast visualization parameters */
float color[4];
float raycast_width;
float destination_size;
int sample_count;
blender::gpu::Batch *raycast_model;
void *draw_handle;
};
static void wm_xr_raycast_destination_draw(const XrRaycastData *data)
{
GPU_depth_test(GPU_DEPTH_LESS_EQUAL);
blender::gpu::Batch *sphere = GPU_batch_preset_sphere(2);
GPU_batch_program_set_builtin(sphere, GPU_SHADER_3D_UNIFORM_COLOR);
GPU_batch_uniform_4fv(sphere, "color", data->color);
GPU_matrix_push();
GPU_matrix_translate_3fv(data->points[data->num_points - 1]);
GPU_matrix_scale_1f(data->destination_size);
GPU_batch_draw(sphere);
GPU_matrix_pop();
}
static void wm_xr_raycast_draw(const bContext * /*C*/, ARegion * /*region*/, void *customdata)
{
const XrRaycastData *data = static_cast<const XrRaycastData *>(customdata);
if (data->result != XR_RAYCAST_MISS) {
wm_xr_raycast_destination_draw(data);
}
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", blender::gpu::VertAttrType::SFLOAT_32_32_32);
@@ -638,32 +689,37 @@ static void wm_xr_raycast_draw(const bContext * /*C*/, ARegion * /*region*/, voi
GPU_depth_test(GPU_DEPTH_NONE);
GPU_point_size(7.0f);
immBegin(GPU_PRIM_POINTS, 1);
immVertex3fv(pos, data->end);
immBegin(GPU_PRIM_POINTS, data->num_points - 1);
for (int i = 1; i < data->num_points; ++i) {
immVertex3fv(pos, data->points[i]);
}
immEnd();
immUnbindProgram();
}
else {
uint col = GPU_vertformat_attr_add(
format, "color", blender::gpu::VertAttrType::SFLOAT_32_32_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_FLAT_COLOR);
BLI_assert(data->raycast_model != nullptr);
float viewport[4];
GPU_viewport_size_get_f(viewport);
immUniform2fv("viewportSize", &viewport[2]);
float forward[3];
float right[3];
immUniform1f("lineWidth", 3.0f * U.pixelsize);
sub_v3_v3v3(forward, data->points[data->num_points - 1], data->points[0]);
copy_v3_fl3(right, forward[1], -forward[0], 0.0f);
normalize_v3(right);
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();
GPU_batch_program_set_builtin(data->raycast_model, GPU_SHADER_XR_RAYCAST);
GPU_batch_uniform_4fv_array(
data->raycast_model, "control_points", XR_MAX_RAYCASTS + 1, data->points);
GPU_batch_uniform_4fv(data->raycast_model, "color", data->color);
GPU_batch_uniform_3fv(data->raycast_model, "right_vector", right);
GPU_batch_uniform_1f(data->raycast_model, "width", data->raycast_width);
GPU_batch_uniform_1i(data->raycast_model, "control_point_count", data->num_points);
GPU_batch_uniform_1i(data->raycast_model, "sample_count", data->sample_count);
GPU_batch_draw(data->raycast_model);
}
immUnbindProgram();
}
static void wm_xr_raycast_init(wmOperator *op)
@@ -712,27 +768,34 @@ static void wm_xr_raycast_update(wmOperator *op,
const wmXrActionData *actiondata)
{
XrRaycastData *data = static_cast<XrRaycastData *>(op->customdata);
float ray_length, axis[3];
float axis[3], nav_scale;
WM_xr_session_state_nav_scale_get(xr, &nav_scale);
data->from_viewer = RNA_boolean_get(op->ptr, "from_viewer");
data->raycast_width = RNA_float_get(op->ptr, "raycast_scale") * nav_scale;
data->sample_count = RNA_int_get(op->ptr, "sample_count");
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_location_get(xr, data->points[0]);
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);
if (!xr->runtime->session_state.raycast_model) {
xr->runtime->session_state.raycast_model = GPU_batch_create_procedural(
GPU_PRIM_TRI_STRIP, 2 * data->sample_count);
}
data->raycast_model = xr->runtime->session_state.raycast_model;
copy_v3_v3(data->points[0], 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,
@@ -779,8 +842,6 @@ static void wm_xr_raycast(Scene *scene,
* controller.
* \{ */
#define XR_DEFAULT_FLY_SPEED_MOVE 0.054f
enum eXrFlyMode {
XR_FLY_FORWARD = 0,
XR_FLY_BACK = 1,
@@ -800,6 +861,9 @@ enum eXrFlyMode {
struct XrFlyData {
float viewer_rot[4];
double time_prev;
/* Only used for snap turn, where the action should be executed only once. */
bool is_finished;
};
static void wm_xr_fly_init(wmOperator *op, const wmXrData *xr)
@@ -946,6 +1010,11 @@ static wmOperatorStatus wm_xr_navigation_fly_exec(bContext * /*C*/, wmOperator *
return OPERATOR_CANCELLED;
}
static void wm_xr_navigation_fly_cancel(bContext * /*C*/, wmOperator *op)
{
wm_xr_fly_uninit(op);
}
static wmOperatorStatus wm_xr_navigation_fly_modal(bContext *C,
wmOperator *op,
const wmEvent *event)
@@ -964,22 +1033,41 @@ static wmOperatorStatus wm_xr_navigation_fly_modal(bContext *C,
wmWindowManager *wm = CTX_wm_manager(C);
wmXrData *xr = &wm->xr;
eXrFlyMode mode;
bool turn, locz_lock, dir_lock, speed_frame_based;
bool turn, snap_turn, invert_rotation, swap_hands, locz_lock, dir_lock, speed_frame_based;
bool speed_interp_cubic = false;
float speed, speed_max, speed_p0[2], speed_p1[2];
float speed, speed_max, speed_p0[2], speed_p1[2], button_state;
GHOST_XrPose nav_pose;
float nav_mat[4][4], delta[4][4], out[4][4];
const double time_now = BLI_time_now_seconds();
const double time_now = BLI_time_now_seconds(), delta_time = time_now - data->time_prev;
data->time_prev = time_now;
mode = (eXrFlyMode)RNA_enum_get(op->ptr, "mode");
swap_hands = xr->runtime->session_state.swap_hands;
mode = (eXrFlyMode)RNA_enum_get(op->ptr, swap_hands ? "alt_mode" : "mode");
turn = ELEM(mode, XR_FLY_TURNLEFT, XR_FLY_TURNRIGHT);
snap_turn = U.xr_navigation.flag & USER_XR_NAV_SNAP_TURN;
invert_rotation = U.xr_navigation.flag & USER_XR_NAV_INVERT_ROTATION;
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");
locz_lock = RNA_boolean_get(op->ptr, swap_hands ? "alt_lock_location_z" : "lock_location_z");
dir_lock = RNA_boolean_get(op->ptr, swap_hands ? "alt_lock_direction" : "lock_direction");
if (turn) {
speed_frame_based = false;
if (snap_turn) {
speed_max = U.xr_navigation.turn_amount;
speed = speed_max;
}
else {
speed_max = U.xr_navigation.turn_speed;
speed = speed_max * RNA_boolean_get(op->ptr, "turn_speed_factor");
}
}
else {
speed_frame_based = RNA_boolean_get(op->ptr, "speed_frame_based");
speed_max = xr->session_settings.fly_speed;
speed = speed_max * RNA_float_get(op->ptr, "fly_speed_factor");
}
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "speed_interpolation0");
if (prop && RNA_property_is_set(op->ptr, prop)) {
@@ -1007,14 +1095,15 @@ static wmOperatorStatus wm_xr_navigation_fly_modal(bContext *C,
/* Interpolate between min/max speeds based on button state. */
switch (actiondata->type) {
case XR_BOOLEAN_INPUT:
button_state = 1.0f;
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);
button_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) /
(button_state - actiondata->float_threshold) /
(1.0f - actiondata->float_threshold) :
1.0f;
if (speed_interp_cubic) {
@@ -1041,21 +1130,29 @@ static wmOperatorStatus wm_xr_navigation_fly_modal(bContext *C,
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) {
if (dir_lock || (snap_turn && data->is_finished) ||
(snap_turn && button_state < RNA_float_get(op->ptr, "snap_turn_threshold")))
{
unit_m4(delta);
}
else {
if (!snap_turn) {
WM_xr_session_state_vignette_activate(xr);
speed *= delta_time;
}
else {
data->is_finished = true;
}
if (invert_rotation) {
speed *= -1.0f;
}
GHOST_XrPose viewer_pose;
float viewer_mat[4][4], nav_inv[4][4];
@@ -1070,10 +1167,16 @@ static wmOperatorStatus wm_xr_navigation_fly_modal(bContext *C,
else {
float nav_scale, ref_quat[4];
WM_xr_session_state_vignette_activate(xr);
/* 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;
if (!speed_frame_based) {
speed *= delta_time;
}
switch (mode) {
/* Move relative to navigation space. */
case XR_FLY_FORWARD:
@@ -1140,6 +1243,7 @@ static void WM_OT_xr_navigation_fly(wmOperatorType *ot)
/* Callbacks. */
ot->invoke = wm_xr_navigation_fly_invoke;
ot->exec = wm_xr_navigation_fly_exec;
ot->cancel = wm_xr_navigation_fly_cancel;
ot->modal = wm_xr_navigation_fly_modal;
ot->poll = wm_xr_operator_sessionactive;
@@ -1179,6 +1283,15 @@ static void WM_OT_xr_navigation_fly(wmOperatorType *ot)
prop = RNA_def_enum(ot->srna, "mode", fly_modes, XR_FLY_VIEWER_FORWARD, "Mode", "Fly mode");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_NAVIGATION);
RNA_def_float(ot->srna,
"snap_turn_threshold",
0.95f,
0.0f,
1.0f,
"Snap Turn Threshold",
"Input state threshold when using snap turn",
0.0f,
1.0f);
RNA_def_boolean(
ot->srna, "lock_location_z", false, "Lock Elevation", "Prevent changes to viewer elevation");
RNA_def_boolean(ot->srna,
@@ -1188,27 +1301,27 @@ static void WM_OT_xr_navigation_fly(wmOperatorType *ot)
"Limit movement to viewer's initial direction");
RNA_def_boolean(ot->srna,
"speed_frame_based",
true,
false,
"Frame Based Speed",
"Apply fixed movement deltas every update");
RNA_def_float(ot->srna,
"speed_min",
XR_DEFAULT_FLY_SPEED_MOVE / 3.0f,
"turn_speed_factor",
1.0 / 3.0f,
0.0f,
1000.0f,
"Minimum Speed",
"Minimum move (turn) speed in meters (radians) per second or frame",
1.0f,
"Turn Speed Factor",
"Ratio between the min and max turn speed",
0.0f,
1000.0f);
1.0f);
RNA_def_float(ot->srna,
"speed_max",
XR_DEFAULT_FLY_SPEED_MOVE,
"fly_speed_factor",
1.0 / 3.0f,
0.0f,
1000.0f,
"Maximum Speed",
"Maximum move (turn) speed in meters (radians) per second or frame",
1.0f,
"Fly Speed Factor",
"Ratio between the min and max fly speed",
0.0f,
1000.0f);
1.0f);
RNA_def_float_vector(ot->srna,
"speed_interpolation0",
2,
@@ -1229,6 +1342,23 @@ static void WM_OT_xr_navigation_fly(wmOperatorType *ot)
"Second cubic spline control point between min/max speeds",
0.0f,
1.0f);
RNA_def_enum(ot->srna,
"alt_mode",
fly_modes,
XR_FLY_VIEWER_FORWARD,
"Mode (Alt)",
"Fly mode when hands are swapped");
RNA_def_boolean(ot->srna,
"alt_lock_location_z",
false,
"Lock Elevation (Alt)",
"When hands are swapped, prevent changes to viewer elevation");
RNA_def_boolean(ot->srna,
"alt_lock_direction",
false,
"Lock Direction (Alt)",
"When hands are swapped, limit movement to viewer's initial direction");
}
/** \} */
@@ -1239,68 +1369,221 @@ static void WM_OT_xr_navigation_fly(wmOperatorType *ot)
* 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)
static float wm_xr_navigation_teleport_pose_calc(wmXrData *xr,
float nav_destination[3],
const float destination[4],
const float normal[3],
const bool teleport_axes[3],
float teleport_t,
float teleport_ofs,
float vertical_ofs)
{
float nav_location[3], nav_rotation[4], viewer_location[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_viewer_pose_location_get(xr, viewer_location);
float nav_axes[3][3], projected[3], v0[3], v1[3], destination_with_ofs[3];
copy_v3_fl(nav_destination, 0.0f);
copy_v3_v3(destination_with_ofs, destination);
destination_with_ofs[2] += vertical_ofs;
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, destination_with_ofs, 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(nav_destination, projected);
}
return len_v3v3(viewer_location, destination);
}
static bool wm_xr_navigation_teleport_ground_plane(float points[XR_MAX_RAYCASTS + 1][4],
int *num_points,
float *ray_dist)
{
constexpr uint z = 2;
for (int i = 1; i < *num_points; ++i) {
float *startpoint = points[i - 1], *endpoint = points[i];
if ((startpoint[z] < 0) == (endpoint[z] < 0)) {
continue;
}
if (startpoint[z] == endpoint[z]) {
break;
}
float segment_ray_dist = len_v3v3(startpoint, endpoint);
float alpha = startpoint[z] / (startpoint[z] - endpoint[z]);
interp_v3_v3v3(endpoint, startpoint, endpoint, alpha);
*ray_dist = segment_ray_dist * (i - 1) + len_v3v3(startpoint, endpoint);
*num_points = i + 1;
return true;
}
return false;
}
static XrRaycastResult wm_xr_navigation_teleport(bContext *C,
wmXrData *xr,
float nav_destination[3],
float points[XR_MAX_RAYCASTS + 1][4],
const float direction[3],
int *num_points,
float *ray_dist,
float *destination_dist,
bool selectable_only,
const bool teleport_axes[3],
float teleport_t,
float teleport_ofs,
float gravity,
float head_height)
{
Scene *scene = CTX_data_scene(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
float location[3];
float normal[3];
int index;
const Object *ob = nullptr;
float obmat[4][4];
wm_xr_raycast(scene,
depsgraph,
origin,
direction,
ray_dist,
selectable_only,
location,
normal,
&index,
&ob,
obmat);
float normal[3], segment_direction[3];
float vertical_ofs = 0;
XrRaycastResult result = XR_RAYCAST_MISS;
/* 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};
copy_v3_v3(segment_direction, direction);
copy_v3_fl3(normal, 0, 1, 0);
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);
/* When ray_dist == 0 or -1, the raycast is a line of infinite length. */
if (*ray_dist <= 0.0f) {
*num_points = 2;
}
wm_xr_basenav_rotation_calc(xr, nav_rotation, nav_rotation);
quat_to_mat3(nav_axes, nav_rotation);
const float segment_length = *ray_dist / (*num_points - 1);
float segment_ray_dist = 0.0f;
*ray_dist = 0.0f;
/* 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);
for (int i = 1; i < *num_points; ++i) {
segment_ray_dist = segment_length;
wm_xr_raycast(scene,
depsgraph,
points[i - 1],
segment_direction,
&segment_ray_dist,
selectable_only,
points[i],
normal,
&index,
&ob,
obmat);
*ray_dist += segment_ray_dist;
if (ob) {
*num_points = i + 1;
/** Ensure normal faces the correct direction */
if (dot_v3v3(segment_direction, normal) > 0) {
mul_v3_fl(normal, -1.0f);
}
/* Add to final location. */
add_v3_v3(out, projected);
result = XR_RAYCAST_HIT;
break;
}
WM_xr_session_state_nav_location_set(xr, out);
madd_v3_v3v3fl(points[i], points[i - 1], segment_direction, segment_length);
/* Apply gravity */
segment_direction[2] -= gravity;
normalize_v3(segment_direction);
}
/** Fall back to raycast intersecting with the ground plane. */
if (result == XR_RAYCAST_MISS) {
vertical_ofs = head_height;
if (wm_xr_navigation_teleport_ground_plane(points, num_points, ray_dist)) {
result = XR_RAYCAST_FALLBACK;
}
}
if (result != XR_RAYCAST_MISS) {
float origin[3], dummy_dest[3], dummy_normal[3];
/* Raycast downward to see if we're on the floor */
copy_v3_fl3(segment_direction, 0, 0, -1);
copy_v3_v3(origin, points[*num_points - 1]);
madd_v3_v3fl(origin, normal, teleport_ofs);
madd_v3_v3fl(origin, segment_direction, -vertical_ofs);
segment_ray_dist = head_height;
ob = nullptr;
wm_xr_raycast(scene,
depsgraph,
origin,
segment_direction,
&segment_ray_dist,
selectable_only,
dummy_dest,
dummy_normal,
&index,
&ob,
obmat);
/* Raycast upward to make sure we don't clip through the ceiling */
if (ob) {
vertical_ofs = head_height - segment_ray_dist;
copy_v3_fl3(segment_direction, 0, 0, 1);
copy_v3_v3(origin, points[*num_points - 1]);
madd_v3_v3fl(origin, normal, teleport_ofs);
segment_ray_dist = vertical_ofs;
ob = nullptr;
wm_xr_raycast(scene,
depsgraph,
origin,
segment_direction,
&segment_ray_dist,
selectable_only,
dummy_dest,
dummy_normal,
&index,
&ob,
obmat);
if (ob) {
vertical_ofs = max_ff(0.0f, segment_ray_dist - teleport_ofs);
}
}
/* Calculate teleportation destination in navigation space */
*destination_dist = wm_xr_navigation_teleport_pose_calc(xr,
nav_destination,
points[*num_points - 1],
normal,
teleport_axes,
teleport_t,
teleport_ofs,
vertical_ofs);
}
return result;
}
static wmOperatorStatus wm_xr_navigation_teleport_invoke(bContext *C,
@@ -1328,6 +1611,11 @@ static wmOperatorStatus wm_xr_navigation_teleport_exec(bContext * /*C*/, wmOpera
return OPERATOR_CANCELLED;
}
static void wm_xr_navigation_teleport_cancel(bContext * /*C*/, wmOperator *op)
{
wm_xr_raycast_uninit(op);
}
static wmOperatorStatus wm_xr_navigation_teleport_modal(bContext *C,
wmOperator *op,
const wmEvent *event)
@@ -1340,32 +1628,66 @@ static wmOperatorStatus wm_xr_navigation_teleport_modal(bContext *C,
wmWindowManager *wm = CTX_wm_manager(C);
wmXrData *xr = &wm->xr;
xr->runtime->session_state.is_raycast_shown = true;
wm_xr_raycast_update(op, xr, actiondata);
XrRaycastData *data = static_cast<XrRaycastData *>(op->customdata);
float nav_scale, ray_dist, destination_dist, nav_destination[3];
bool teleport_axes[3];
WM_xr_session_state_nav_scale_get(xr, &nav_scale);
RNA_boolean_get_array(op->ptr, "teleport_axes", teleport_axes);
const float teleport_t = RNA_float_get(op->ptr, "interpolation");
const float teleport_ofs = RNA_float_get(op->ptr, "offset") * nav_scale;
const float gravity = RNA_float_get(op->ptr, "gravity");
const float head_height = xr->runtime->session_state.prev_local_pose.position[1] * nav_scale;
const bool selectable_only = RNA_boolean_get(op->ptr, "selectable_only");
ray_dist = RNA_float_get(op->ptr, "distance") * nav_scale;
data->num_points = XR_MAX_RAYCASTS + 1;
data->result = wm_xr_navigation_teleport(C,
xr,
nav_destination,
data->points,
data->direction,
&data->num_points,
&ray_dist,
&destination_dist,
selectable_only,
teleport_axes,
teleport_t,
teleport_ofs,
gravity,
head_height);
data->destination_size = RNA_float_get(op->ptr, "destination_scale") *
sqrt(destination_dist / nav_scale) * nav_scale;
switch (data->result) {
case XR_RAYCAST_MISS:
RNA_float_get_array(op->ptr, "miss_color", data->color);
break;
case XR_RAYCAST_HIT:
RNA_float_get_array(op->ptr, "hit_color", data->color);
break;
case XR_RAYCAST_FALLBACK:
RNA_float_get_array(op->ptr, "fallback_color", data->color);
break;
default:
BLI_assert_unreachable();
break;
}
switch (event->val) {
case KM_PRESS:
return OPERATOR_RUNNING_MODAL;
case KM_RELEASE: {
XrRaycastData *data = static_cast<XrRaycastData *>(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);
if (data->result != XR_RAYCAST_MISS) {
WM_xr_session_state_nav_location_set(xr, nav_destination);
}
xr->runtime->session_state.is_raycast_shown = false;
wm_xr_raycast_uninit(op);
return OPERATOR_FINISHED;
@@ -1388,6 +1710,7 @@ static void WM_OT_xr_navigation_teleport(wmOperatorType *ot)
/* Callbacks. */
ot->invoke = wm_xr_navigation_teleport_invoke;
ot->exec = wm_xr_navigation_teleport_exec;
ot->cancel = wm_xr_navigation_teleport_cancel;
ot->modal = wm_xr_navigation_teleport_modal;
ot->poll = wm_xr_operator_sessionactive;
@@ -1411,7 +1734,7 @@ static void WM_OT_xr_navigation_teleport(wmOperatorType *ot)
1.0f);
RNA_def_float(ot->srna,
"offset",
0.0f,
0.25f,
0.0f,
FLT_MAX,
"Offset",
@@ -1425,13 +1748,49 @@ static void WM_OT_xr_navigation_teleport(wmOperatorType *ot)
"Only allow selectable objects to influence raycast result");
RNA_def_float(ot->srna,
"distance",
BVH_RAYCAST_DIST_MAX,
80.0,
0.0,
BVH_RAYCAST_DIST_MAX,
"",
"Maximum raycast distance",
0.0,
BVH_RAYCAST_DIST_MAX);
RNA_def_float(ot->srna,
"gravity",
0.1,
0.0,
FLT_MAX,
"Gravity",
"Downward curvature applied to raycast",
0.0,
FLT_MAX);
RNA_def_float(ot->srna,
"raycast_scale",
0.02f,
0.0f,
FLT_MAX,
"Raycast Scale",
"Width of the raycast visualization",
0.0f,
FLT_MAX);
RNA_def_float(ot->srna,
"destination_scale",
0.05f,
0.0f,
FLT_MAX,
"Destination Scale",
"Width of the destination visualization",
0.0f,
FLT_MAX);
RNA_def_int(ot->srna,
"sample_count",
48,
2,
INT_MAX,
"Sample Count",
"Number of interpolation samples for the raycast visualization",
2,
INT_MAX);
RNA_def_boolean(
ot->srna, "from_viewer", false, "From Viewer", "Use viewer pose as raycast origin");
RNA_def_float_vector(ot->srna,
@@ -1445,13 +1804,33 @@ static void WM_OT_xr_navigation_teleport(wmOperatorType *ot)
-1.0f,
1.0f);
RNA_def_float_color(ot->srna,
"color",
"hit_color",
4,
g_xr_default_raycast_color,
g_xr_default_raycast_hit_color,
0.0f,
1.0f,
"Color",
"Raycast color",
"Hit Color",
"Color of raycast when it succeeds",
0.0f,
1.0f);
RNA_def_float_color(ot->srna,
"miss_color",
4,
g_xr_default_raycast_miss_color,
0.0f,
1.0f,
"Miss Color",
"Color of raycast when it misses",
0.0f,
1.0f);
RNA_def_float_color(ot->srna,
"fallback_color",
4,
g_xr_default_raycast_fallback_color,
0.0f,
1.0f,
"Fallback Color",
"Color of raycast when a fallback case succeeds",
0.0f,
1.0f);
}
@@ -1547,6 +1926,74 @@ static void WM_OT_xr_navigation_reset(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name XR Navigation Swap Hands
*
* Resets XR navigation deltas relative to session base pose.
* \{ */
static wmOperatorStatus wm_xr_navigation_swap_hands_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
if (!wm_xr_operator_test_event(op, event)) {
return OPERATOR_PASS_THROUGH;
}
WM_event_add_modal_handler(C, op);
wmWindowManager *wm = CTX_wm_manager(C);
wmXrData *xr = &wm->xr;
xr->runtime->session_state.swap_hands = true;
return OPERATOR_RUNNING_MODAL;
}
static wmOperatorStatus wm_xr_navigation_swap_hands_exec(bContext * /*C*/, wmOperator * /*op*/)
{
return OPERATOR_CANCELLED;
}
static wmOperatorStatus wm_xr_navigation_swap_hands_modal(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);
wmXrData *xr = &wm->xr;
switch (event->val) {
case KM_PRESS:
return OPERATOR_RUNNING_MODAL;
case KM_RELEASE:
xr->runtime->session_state.swap_hands = false;
return OPERATOR_FINISHED;
default:
BLI_assert_unreachable();
return OPERATOR_CANCELLED;
}
}
static void WM_OT_xr_navigation_swap_hands(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "XR Navigation Swap Hands";
ot->idname = "WM_OT_xr_navigation_swap_hands";
ot->description = "Swap VR navigation controls between left / right controllers";
/* Callbacks. */
ot->invoke = wm_xr_navigation_swap_hands_invoke;
ot->exec = wm_xr_navigation_swap_hands_exec;
ot->modal = wm_xr_navigation_swap_hands_modal;
ot->poll = wm_xr_operator_sessionactive;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Operator Registration
* \{ */
@@ -1558,6 +2005,7 @@ void wm_xr_operatortypes_register()
WM_operatortype_append(WM_OT_xr_navigation_fly);
WM_operatortype_append(WM_OT_xr_navigation_teleport);
WM_operatortype_append(WM_OT_xr_navigation_reset);
WM_operatortype_append(WM_OT_xr_navigation_swap_hands);
}
/** \} */

View File

@@ -71,6 +71,10 @@ static void wm_xr_session_create_cb()
settings->base_scale = 1.0f;
}
state->prev_base_scale = settings->base_scale;
/* Initialize vignette. */
state->vignette_data = MEM_callocN<wmXrVignetteData>(__func__);
WM_xr_session_state_vignette_reset(state);
}
static void wm_xr_session_controller_data_free(wmXrSessionState *state)
@@ -84,9 +88,27 @@ static void wm_xr_session_controller_data_free(wmXrSessionState *state)
}
}
static void wm_xr_session_vignette_data_free(wmXrSessionState *state)
{
if (state->vignette_data) {
MEM_freeN(state->vignette_data);
state->vignette_data = nullptr;
}
}
static void wm_xr_session_raycast_model_free(wmXrSessionState *state)
{
if (state->raycast_model) {
GPU_batch_discard(state->raycast_model);
state->raycast_model = nullptr;
}
}
void wm_xr_session_data_free(wmXrSessionState *state)
{
wm_xr_session_controller_data_free(state);
wm_xr_session_vignette_data_free(state);
wm_xr_session_raycast_model_free(state);
}
static void wm_xr_session_exit_cb(void *customdata)
@@ -384,6 +406,8 @@ void wm_xr_session_state_update(const XrSessionSettings *settings,
state->is_view_data_set = true;
/* Assume this was already done through wm_xr_session_draw_data_update(). */
state->force_reset_to_base_pose = false;
WM_xr_session_state_vignette_update(state);
}
wmXrSessionState *WM_xr_session_state_handle_get(const wmXrData *xr)
@@ -572,6 +596,52 @@ void WM_xr_session_state_navigation_reset(wmXrSessionState *state)
unit_qt(state->nav_pose.orientation_quat);
state->nav_scale = 1.0f;
state->is_navigation_dirty = true;
state->swap_hands = false;
}
void WM_xr_session_state_vignette_reset(wmXrSessionState *state)
{
wmXrVignetteData *data = state->vignette_data;
/* Reset vignette state */
data->aperture = 1.0f;
data->aperture_velocity = 0.0f;
/* Set default vignette parameters */
data->initial_aperture = 0.25f;
data->initial_aperture_velocity = -0.03f;
data->aperture_min = 0.08f;
data->aperture_max = 0.3f;
data->aperture_velocity_max = 0.002f;
data->aperture_velocity_delta = 0.01f;
}
void WM_xr_session_state_vignette_activate(wmXrData *xr)
{
if (WM_xr_session_exists(xr)) {
wmXrVignetteData *data = xr->runtime->session_state.vignette_data;
data->aperture_velocity = data->initial_aperture_velocity;
data->aperture = min_ff(data->aperture, data->initial_aperture);
}
}
void WM_xr_session_state_vignette_update(wmXrSessionState *state)
{
wmXrVignetteData *data = state->vignette_data;
const float vignette_intensity = U.xr_navigation.vignette_intensity;
const float aperture_min = interpf(
data->aperture_min, data->aperture_max, vignette_intensity * 0.01f);
data->aperture_velocity = min_ff(data->aperture_velocity_max,
data->aperture_velocity + data->aperture_velocity_delta);
if (data->aperture == aperture_min) {
data->aperture_velocity = data->aperture_velocity_max;
}
data->aperture = clamp_f(data->aperture + data->aperture_velocity, aperture_min, 1.0f);
}
/* -------------------------------------------------------------------- */