From f0fdc8e8217aecc669cab3b0740a9296c1739f0e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 4 Apr 2023 15:35:18 +0200 Subject: [PATCH] Better handle scroll-to-zoom in 3D views Problem: when radius shrinks, scrolling no longer does much. This makes the user think something is broken. Solution: switch to dolly when radius is small enough. That way scrolling will always move you closer, one way or another. --- crates/re_space_view_spatial/src/eye.rs | 25 +++++++++++++++++------ crates/re_space_view_spatial/src/ui_3d.rs | 3 ++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/re_space_view_spatial/src/eye.rs b/crates/re_space_view_spatial/src/eye.rs index 5b929ef122e3..64883afa5ca9 100644 --- a/crates/re_space_view_spatial/src/eye.rs +++ b/crates/re_space_view_spatial/src/eye.rs @@ -1,6 +1,6 @@ use egui::{lerp, NumExt as _, Rect}; use glam::Affine3A; -use macaw::{vec3, IsoTransform, Mat4, Quat, Vec3}; +use macaw::{vec3, BoundingBox, IsoTransform, Mat4, Quat, Vec3}; use re_space_view::controls::{ DRAG_PAN3D_BUTTON, ROLL_MOUSE, ROLL_MOUSE_ALT, ROLL_MOUSE_MODIFIER, ROTATE3D_BUTTON, @@ -300,7 +300,12 @@ impl OrbitEye { /// Returns `true` if interaction occurred. /// I.e. the camera changed via user input. - pub fn update(&mut self, response: &egui::Response, drag_threshold: f32) -> bool { + pub fn update( + &mut self, + response: &egui::Response, + drag_threshold: f32, + scene_bbox: &BoundingBox, + ) -> bool { let mut did_interact = false; if response.drag_delta().length() > drag_threshold { @@ -355,10 +360,18 @@ impl OrbitEye { if zoom_factor != 1.0 { let new_radius = self.orbit_radius / zoom_factor; - // Don't let radius go too small or too big because this might cause infinity/nan in some calculations. - // Max value is chosen with some generous margin of an observed crash due to infinity. - if f32::MIN_POSITIVE < new_radius && new_radius < 1.0e17 { - self.orbit_radius = new_radius; + let very_close = scene_bbox.size().length() / 100.0; + if very_close.is_finite() && new_radius < very_close && 1.0 < zoom_factor { + // The user may be scrolling to move the camera closer, but are not realizing + // the radius is now tiny. + // Switch to instead dolly the camera forward: + self.orbit_center += self.fwd() * very_close * zoom_factor.ln(); + } else { + // Don't let radius go too small or too big because this might cause infinity/nan in some calculations. + // Max value is chosen with some generous margin of an observed crash due to infinity. + if f32::MIN_POSITIVE < new_radius && new_radius < 1.0e17 { + self.orbit_radius = new_radius; + } } } diff --git a/crates/re_space_view_spatial/src/ui_3d.rs b/crates/re_space_view_spatial/src/ui_3d.rs index 8fafd88f4104..3248270ea60d 100644 --- a/crates/re_space_view_spatial/src/ui_3d.rs +++ b/crates/re_space_view_spatial/src/ui_3d.rs @@ -306,7 +306,8 @@ pub fn view_3d( let orbit_eye = state .state_3d .update_eye(&response, &state.scene_bbox_accum, space_cameras); - let did_interact_with_eye = orbit_eye.update(&response, orbit_eye_drag_threshold); + let did_interact_with_eye = + orbit_eye.update(&response, orbit_eye_drag_threshold, &state.scene_bbox_accum); let orbit_eye = *orbit_eye; let eye = orbit_eye.to_eye();