Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it easier to position 3D eye-camera center #4943

Merged
merged 5 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/re_space_view_spatial/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ parking_lot.workspace = true
rayon.workspace = true
serde.workspace = true
smallvec = { workspace = true, features = ["serde"] }
web-time.workspace = true


[dev-dependencies]
Expand Down
50 changes: 26 additions & 24 deletions crates/re_space_view_spatial/src/eye.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use egui::{lerp, NumExt as _, Rect};
use glam::Affine3A;
use macaw::{vec3, BoundingBox, IsoTransform, Mat4, Quat, Vec3};
use macaw::{vec3, IsoTransform, Mat4, Quat, Vec3};

use re_space_view::controls::{
RuntimeModifiers, DRAG_PAN3D_BUTTON, ROLL_MOUSE, ROLL_MOUSE_ALT, ROLL_MOUSE_MODIFIER,
Expand Down Expand Up @@ -266,12 +266,7 @@ 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,
scene_bbox: &BoundingBox,
) -> bool {
pub fn update(&mut self, response: &egui::Response, drag_threshold: f32) -> bool {
// Dragging even below the [`drag_threshold`] should be considered interaction.
// Otherwise we flicker in and out of "has interacted" too quickly.
let mut did_interact = response.drag_delta().length() > 0.0;
Expand All @@ -294,7 +289,7 @@ impl OrbitEye {
}

let (zoom_delta, scroll_delta) = if response.hovered() {
self.keyboard_navigation(&response.ctx);
did_interact |= self.keyboard_navigation(&response.ctx);
response
.ctx
.input(|i| (i.zoom_delta(), i.smooth_scroll_delta.y))
Expand All @@ -309,34 +304,36 @@ impl OrbitEye {
if zoom_factor != 1.0 {
let new_radius = self.orbit_radius / zoom_factor;

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;
}
// The user may be scrolling to move the camera closer, but are not realizing
// the radius is now tiny.
// TODO(emilk): inform the users somehow that scrolling won't help, and that they should use WSAD instead.
// It might be tempting to start moving the camera here on scroll, but that would is bad for other reasons.

// 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;
}
}

did_interact
}

/// Listen to WSAD and QE to move the eye.
fn keyboard_navigation(&mut self, egui_ctx: &egui::Context) {
///
/// Returns `true` if we did anything.
fn keyboard_navigation(&mut self, egui_ctx: &egui::Context) -> bool {
let anything_has_focus = egui_ctx.memory(|mem| mem.focus().is_some());
if anything_has_focus {
return; // e.g. we're typing in a TextField
return false; // e.g. we're typing in a TextField
}

let os = egui_ctx.os();

let requires_repaint = egui_ctx.input(|input| {
let mut did_interact = false;
let mut requires_repaint = false;

egui_ctx.input(|input| {
let dt = input.stable_dt.at_most(0.1);

// X=right, Y=up, Z=back
Expand Down Expand Up @@ -367,12 +364,17 @@ impl OrbitEye {
egui::emath::exponential_smooth_factor(0.90, 0.2, dt),
);
self.orbit_center += self.velocity * dt;
local_movement != Vec3::ZERO || self.velocity.length() > 0.01 * speed

did_interact = local_movement != Vec3::ZERO;
requires_repaint =
local_movement != Vec3::ZERO || self.velocity.length() > 0.01 * speed;
});

if requires_repaint {
egui_ctx.request_repaint();
}

did_interact
}

/// Rotate based on a certain number of pixel delta.
Expand Down
45 changes: 25 additions & 20 deletions crates/re_space_view_spatial/src/ui_3d.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use egui::{emath::RectTransform, NumExt as _};
use glam::Affine3A;
use macaw::{vec3, BoundingBox, Quat, Vec3};
use web_time::Instant;

use re_log_types::EntityPath;
use re_renderer::{
Expand Down Expand Up @@ -36,7 +37,10 @@ use super::eye::{Eye, OrbitEye};
#[derive(Clone)]
pub struct View3DState {
pub orbit_eye: Option<OrbitEye>,
pub did_interact_with_eye: bool,

/// Used to show the orbit center of the eye-camera when the user interacts.
/// None: user has never interacted with the eye-camera.
pub last_eye_interaction: Option<Instant>,

/// Currently tracked entity.
///
Expand All @@ -62,7 +66,7 @@ impl Default for View3DState {
fn default() -> Self {
Self {
orbit_eye: Default::default(),
did_interact_with_eye: false,
last_eye_interaction: None,
tracked_entity: None,
camera_before_tracked_entity: None,
eye_interpolation: Default::default(),
Expand Down Expand Up @@ -101,7 +105,7 @@ impl View3DState {
// If the user has not interacted with the eye-camera yet, continue to
// interpolate to the new default eye. This gives much better robustness
// with scenes that grow over time.
if !self.did_interact_with_eye {
if self.last_eye_interaction.is_none() {
self.interpolate_to_orbit_eye(default_eye(
&bounding_boxes.accumulated,
&view_coordinates,
Expand Down Expand Up @@ -174,12 +178,8 @@ impl View3DState {
0.0
};

if orbit_eye.update(
response,
orbit_eye_drag_threshold,
&bounding_boxes.accumulated,
) {
self.did_interact_with_eye = true;
if orbit_eye.update(response, orbit_eye_drag_threshold) {
self.last_eye_interaction = Some(Instant::now());
self.eye_interpolation = None;
self.tracked_entity = None;
self.camera_before_tracked_entity = None;
Expand Down Expand Up @@ -304,8 +304,10 @@ impl View3DState {
}

pub fn set_spin(&mut self, spin: bool) {
self.spin = spin;
self.did_interact_with_eye = true;
if spin != self.spin {
self.spin = spin;
self.last_eye_interaction = Some(Instant::now());
}
}
}

Expand Down Expand Up @@ -547,7 +549,7 @@ pub fn view_3d(
}
};
if let Some(entity_path) = focused_entity {
state.state_3d.did_interact_with_eye = true;
state.state_3d.last_eye_interaction = Some(Instant::now());

// TODO(#4812): We currently only track cameras on double click since tracking arbitrary entities was deemed too surprising.
if find_camera(space_cameras, entity_path).is_some() {
Expand Down Expand Up @@ -630,18 +632,21 @@ pub fn view_3d(
let ui_time = ui.input(|i| i.time);
let any_mouse_button_down = ui.input(|i| i.pointer.any_down());

// Don't show for merely scrolling.
// Scroll events from a mouse wheel often happen with some pause between meaning we either need a long delay for the center to show
// or live with the flickering.
let should_show_center_of_orbit_camera =
state.state_3d.did_interact_with_eye && any_mouse_button_down;
let should_show_center_of_orbit_camera = state
.state_3d
.last_eye_interaction
.map_or(false, |d| d.elapsed().as_secs_f32() < 0.35);

if should_show_center_of_orbit_camera && !state.state_3d.eye_interact_fade_in {
if !state.state_3d.eye_interact_fade_in && should_show_center_of_orbit_camera {
// Any interaction immediately causes fade in to start if it's not already on.
state.state_3d.eye_interact_fade_change_time = ui_time;
state.state_3d.eye_interact_fade_in = true;
} else if state.state_3d.eye_interact_fade_in && !any_mouse_button_down {
// Fade out on the other hand only happens if no mouse cursor is pressed.
}
if state.state_3d.eye_interact_fade_in
&& !should_show_center_of_orbit_camera
// Don't start fade-out while dragging, even if mouse is still
&& !any_mouse_button_down
{
state.state_3d.eye_interact_fade_change_time = ui_time;
state.state_3d.eye_interact_fade_in = false;
}
Expand Down
Loading