From b827c8cd1ec9d9511e9b2d149bcd36df0908bb4b Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 16 Apr 2023 16:24:41 +1000 Subject: [PATCH] Fix #104385: Unexpected clipping in ortho view & orbit around selection Orbit around selection didn't work well in orthographic views, potentially causing viewport offset to drift during navigation to the point content would be outside the far clipping range. Resolve by aligning the view offset depth with the dynamic offset being orbited around. --- .../editors/space_view3d/view3d_navigate.cc | 54 +++++++++++++++++++ .../editors/space_view3d/view3d_navigate.h | 9 ++++ .../space_view3d/view3d_navigate_rotate.c | 4 ++ 3 files changed, 67 insertions(+) diff --git a/source/blender/editors/space_view3d/view3d_navigate.cc b/source/blender/editors/space_view3d/view3d_navigate.cc index 45618b67172..11db6a3f5bf 100644 --- a/source/blender/editors/space_view3d/view3d_navigate.cc +++ b/source/blender/editors/space_view3d/view3d_navigate.cc @@ -160,12 +160,66 @@ void view3d_orbit_apply_dyn_ofs(float r_ofs[3], add_v3_v3(r_ofs, dyn_ofs); } +static void view3d_orbit_apply_dyn_ofs_ortho_correction(float ofs[3], + const float viewquat_old[4], + const float viewquat_new[4], + const float dyn_ofs[3]) +{ + /* NOTE(@ideasman42): While orbiting in orthographic mode the "depth" of the offset + * (position along the views Z-axis) is only noticeable when the view contents is clipped. + * The likelihood of clipping depends on the clipping range & size of the scene. + * In practice some users might not run into this, however using dynamic-offset in + * orthographic views can cause the depth of the offset to drift while navigating the view, + * causing unexpected clipping that seems like a bug from the user perspective, see: #104385. + * + * Imagine a camera is focused on a distant object. Now imagine a closer object in front of + * the camera is used as a pivot, the camera is rotated to view it from the side (~90d rotation). + * The outcome is the DOF is now focused on a distant region to the left/right. + * The new focal point is unlikely to point to anything useful (unless by accident). + * Instead of a focal point - the `rv3d->ofs` is being manipulated in this case. + * + * Resolve by moving #RegionView3D::ofs so it is depth-aligned to `dyn_ofs`, + * this is interpolated by the amount of rotation so minor rotations don't cause + * the view-clipping to suddenly jump. */ + + float q_inv[4]; + + float view_z_init[3] = {0.0f, 0.0f, 1.0f}; + invert_qt_qt_normalized(q_inv, viewquat_old); + mul_qt_v3(q_inv, view_z_init); + + float view_z_curr[3] = {0.0f, 0.0f, 1.0f}; + invert_qt_qt_normalized(q_inv, viewquat_new); + mul_qt_v3(q_inv, view_z_curr); + + const float angle_cos = max_ff(0.0f, dot_v3v3(view_z_init, view_z_curr)); + /* 1.0 or more means no rotation, there is nothing to do in that case. */ + if (LIKELY(angle_cos < 1.0f)) { + const float dot_ofs_curr = dot_v3v3(view_z_curr, ofs); + const float dot_ofs_next = dot_v3v3(view_z_curr, dyn_ofs); + const float ofs_delta = dot_ofs_next - dot_ofs_curr; + if (LIKELY(ofs_delta != 0.0f)) { + /* Calculate a factor where 0.0 represents no rotation and 1.0 represents 90d or more. + * NOTE: Without applying the factor, the distances immediately changes + * (useful for testing), but not good for the users experience as minor rotations + * should not immediately adjust the depth. */ + const float factor = acosf(angle_cos) / M_PI_2; + madd_v3_v3fl(ofs, view_z_curr, ofs_delta * factor); + } + } +} + void viewrotate_apply_dyn_ofs(ViewOpsData *vod, const float viewquat_new[4]) { if (vod->use_dyn_ofs) { RegionView3D *rv3d = vod->rv3d; view3d_orbit_apply_dyn_ofs( rv3d->ofs, vod->init.ofs, vod->init.quat, viewquat_new, vod->dyn_ofs); + + if (vod->use_dyn_ofs_ortho_correction) { + view3d_orbit_apply_dyn_ofs_ortho_correction( + rv3d->ofs, vod->init.quat, viewquat_new, vod->dyn_ofs); + } } } diff --git a/source/blender/editors/space_view3d/view3d_navigate.h b/source/blender/editors/space_view3d/view3d_navigate.h index c0efb314e75..4954b294051 100644 --- a/source/blender/editors/space_view3d/view3d_navigate.h +++ b/source/blender/editors/space_view3d/view3d_navigate.h @@ -151,6 +151,15 @@ typedef struct ViewOpsData { /** Use for orbit selection and auto-dist. */ float dyn_ofs[3]; bool use_dyn_ofs; + + /** + * In orthographic views, a dynamic offset should not cause #RegionView3D::ofs to end up + * at a location that has no relation to the content where `ofs` originated or to `dyn_ofs`. + * Failing to do so can cause the orthographic views `ofs` to be far away from the content + * to the point it gets clipped out of the view. + * See #view3d_orbit_apply_dyn_ofs code-comments for an example, also see: #104385. + */ + bool use_dyn_ofs_ortho_correction; } ViewOpsData; /* view3d_navigate.cc */ diff --git a/source/blender/editors/space_view3d/view3d_navigate_rotate.c b/source/blender/editors/space_view3d/view3d_navigate_rotate.c index b7fee5538ab..dd4a5c518a3 100644 --- a/source/blender/editors/space_view3d/view3d_navigate_rotate.c +++ b/source/blender/editors/space_view3d/view3d_navigate_rotate.c @@ -430,6 +430,10 @@ static int viewrotate_invoke(bContext *C, wmOperator *op, const wmEvent *event) viewops_flag_from_prefs() | VIEWOPS_FLAG_PERSP_ENSURE | (use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0)); + if (vod->use_dyn_ofs && (vod->rv3d->is_persp == false)) { + vod->use_dyn_ofs_ortho_correction = true; + } + ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->region); if (ELEM(event->type, MOUSEPAN, MOUSEROTATE)) {