From 74d918d0faf44d9ca39d8d6c9f4b8ee20016415f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 6 Feb 2024 21:08:38 +0100 Subject: [PATCH] Fix forever repaint of big scenes (#5071) ### What * Closes https://github.com/rerun-io/rerun/issues/5018 When first loading a scene, before the user interacts, we want to center the eye-camera on the scene, so each frame we call "interpolate to the default eye". This should be fine, because we have an early-out if we're already at target. However, the interpolation code had a rounding error meaning we reached _very close_ to our goal, but not quite there. In particular, in `arkit_scenes` it would lead to a forever repaint, since the eye-camera would never reach its target, and so thus would require more animation. This manifested in `arkit_scenes` now due to another bug (https://github.com/rerun-io/rerun/issues/5070) causing enormous point clouds, making the rounding errors larger than usual. ### Checklist * [x] I have read and agree to [Contributor Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md) * [x] I've included a screenshot or gif (if applicable) * [x] I have tested the web demo (if applicable): * Using newly built examples: [app.rerun.io](https://app.rerun.io/pr/5071/index.html) * Using examples from latest `main` build: [app.rerun.io](https://app.rerun.io/pr/5071/index.html?manifest_url=https://app.rerun.io/version/main/examples_manifest.json) * Using full set of examples from `nightly` build: [app.rerun.io](https://app.rerun.io/pr/5071/index.html?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json) * [x] The PR title and labels are set such as to maximize their usefulness for the next release's CHANGELOG * [x] If applicable, add a new check to the [release checklist](tests/python/release_checklist)! - [PR Build Summary](https://build.rerun.io/pr/5071) - [Docs preview](https://rerun.io/preview/d20d34685e6aaba1daf5c6417e1bb39d22448d40/docs) - [Examples preview](https://rerun.io/preview/d20d34685e6aaba1daf5c6417e1bb39d22448d40/examples) - [Recent benchmark results](https://build.rerun.io/graphs/crates.html) - [Wasm size tracking](https://build.rerun.io/graphs/sizes.html) --- crates/re_space_view_spatial/src/eye.rs | 20 +++++++++++++------- crates/re_space_view_spatial/src/ui_3d.rs | 19 ++++++++++--------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/crates/re_space_view_spatial/src/eye.rs b/crates/re_space_view_spatial/src/eye.rs index 9fead86394a7..07efc7896157 100644 --- a/crates/re_space_view_spatial/src/eye.rs +++ b/crates/re_space_view_spatial/src/eye.rs @@ -209,13 +209,19 @@ impl OrbitEye { } pub fn lerp(&self, other: &Self, t: f32) -> Self { - Self { - orbit_center: self.orbit_center.lerp(other.orbit_center, t), - orbit_radius: lerp(self.orbit_radius..=other.orbit_radius, t), - world_from_view_rot: self.world_from_view_rot.slerp(other.world_from_view_rot, t), - fov_y: egui::lerp(self.fov_y..=other.fov_y, t), - up: self.up.lerp(other.up, t).normalize_or_zero(), - velocity: self.velocity.lerp(other.velocity, t), + if t == 0.0 { + *self // avoid rounding errors + } else if t == 1.0 { + *other // avoid rounding errors + } else { + Self { + orbit_center: self.orbit_center.lerp(other.orbit_center, t), + orbit_radius: lerp(self.orbit_radius..=other.orbit_radius, t), + world_from_view_rot: self.world_from_view_rot.slerp(other.world_from_view_rot, t), + fov_y: egui::lerp(self.fov_y..=other.fov_y, t), + up: self.up.lerp(other.up, t).normalize_or_zero(), + velocity: self.velocity.lerp(other.velocity, t), + } } } diff --git a/crates/re_space_view_spatial/src/ui_3d.rs b/crates/re_space_view_spatial/src/ui_3d.rs index dbe604b725ee..d06fee7aedbf 100644 --- a/crates/re_space_view_spatial/src/ui_3d.rs +++ b/crates/re_space_view_spatial/src/ui_3d.rs @@ -147,10 +147,6 @@ impl View3DState { let t = t.clamp(0.0, 1.0); let t = ease_out(t); - if t < 1.0 { - response.ctx.request_repaint(); - } - if let Some(target_orbit) = &cam_interpolation.target_orbit { *orbit_eye = cam_interpolation.start.lerp(target_orbit, t); } else if let Some(target_eye) = &cam_interpolation.target_eye { @@ -160,12 +156,12 @@ impl View3DState { self.eye_interpolation = None; } - if 1.0 <= t { - // We have arrived at our target - self.eye_interpolation = None; - } else { + if t < 1.0 { // There's more frames to render to finish interpolation. response.ctx.request_repaint(); + } else { + // We have arrived at our target + self.eye_interpolation = None; } } @@ -256,6 +252,10 @@ impl View3DState { // the user wants to move the camera somewhere, so stop spinning self.spin = false; + if self.orbit_eye == Some(target) { + return; // We're already there. + } + // Don't restart interpolation if we're already on it. if let Some(eye_interpolation) = &self.eye_interpolation { if eye_interpolation.target_orbit == Some(target) { @@ -329,7 +329,8 @@ impl EyeInterpolation { .angle_between(stop.world_from_rub_view.rotation()); // Threshold to avoid doing pointless interpolations that trigger frame requests. - if angle_difference < 0.01 && start.pos_in_world().distance(stop.pos_in_world()) < 0.0001 { + let distance = start.pos_in_world().distance(stop.pos_in_world()); + if angle_difference < 0.01 && distance < 0.0001 { None } else { Some(egui::remap_clamp(