From da21de250e64036f5ed519cf22b013f1a8a4548b Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Sat, 6 Jan 2024 02:49:36 -0800 Subject: [PATCH] framerate independent smoothing --- .../pisa_diffuse_rgb9e5_zstd.ktx2 | Bin 0 -> 23305 bytes examples/demo.rs | 9 +- src/cam_component.rs | 254 ++++++++++++------ src/dolly_zoom.rs | 19 +- src/input.rs | 2 +- src/plugin.rs | 9 +- 6 files changed, 199 insertions(+), 94 deletions(-) create mode 100644 assets/environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2 diff --git a/assets/environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2 b/assets/environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2 new file mode 100644 index 0000000000000000000000000000000000000000..e260df24bd189eff7b0aacb647f702285cffe2fa GIT binary patch literal 23305 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 F>!O;2$}Ru^ literal 0 HcmV?d00001 diff --git a/examples/demo.rs b/examples/demo.rs index 05e154a..b83b6f6 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -1,4 +1,4 @@ -use std::time::Duration; +use std::{thread::sleep, time::Duration}; use bevy::{ core_pipeline::{bloom::BloomSettings, tonemapping::Tonemapping}, @@ -8,7 +8,7 @@ use bevy_editor_cam::{prelude::*, skybox::SkyboxCamConfig}; fn main() { App::new() - .insert_resource(bevy::winit::WinitSettings::desktop_app()) + // .insert_resource(bevy::winit::WinitSettings::desktop_app()) .add_plugins(( DefaultPlugins, bevy_mod_picking::DefaultPickingPlugins, @@ -24,6 +24,7 @@ fn send_events(keyboard: Res>) { if keyboard.just_pressed(KeyCode::P) { // cam_events.send(ChangeProjection::To); } + sleep(Duration::from_millis(0)); } fn setup(mut commands: Commands, asset_server: Res) { @@ -89,8 +90,8 @@ fn setup(mut commands: Commands, asset_server: Res) { // OrbitMode::Free, Smoothness { pan: Duration::from_millis(20), - orbit: Duration::from_millis(60), - zoom: Duration::from_millis(80), + orbit: Duration::from_millis(40), + zoom: Duration::from_millis(100), }, Sensitivity::same(1.0), Momentum { diff --git a/src/cam_component.rs b/src/cam_component.rs index dc18ac5..383c4c9 100644 --- a/src/cam_component.rs +++ b/src/cam_component.rs @@ -1,17 +1,12 @@ use std::{ collections::VecDeque, f32::consts::{FRAC_PI_2, PI}, - sync::{OnceLock, RwLock}, + ops::{Add, AddAssign, Mul}, time::Duration, }; use bevy::{ - diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}, - ecs::{ - component::Component, - event::EventWriter, - system::{Query, Res}, - }, + ecs::{component::Component, event::EventWriter, system::Query}, gizmos::gizmos::Gizmos, log::error, math::{DVec2, DVec3, Quat, Vec2, Vec3}, @@ -21,6 +16,7 @@ use bevy::{ color::Color, }, transform::components::Transform, + utils::Instant, window::RequestRedraw, }; @@ -107,8 +103,8 @@ impl EditorCam { self.motion = Motion::Active { anchor: self.anchor_or_fallback(anchor), motion_inputs: MotionInputs::OrbitZoom { - movement: VecDeque::new(), - zoom_inputs: VecDeque::new(), + movement: InputQueue::default(), + zoom_inputs: InputQueue::default(), }, } } @@ -117,8 +113,8 @@ impl EditorCam { self.motion = Motion::Active { anchor: self.anchor_or_fallback(anchor), motion_inputs: MotionInputs::PanZoom { - movement: VecDeque::new(), - zoom_inputs: VecDeque::new(), + movement: InputQueue::default(), + zoom_inputs: InputQueue::default(), }, } } @@ -128,11 +124,11 @@ impl EditorCam { // Inherit current camera velocity let zoom_inputs = match self.motion { Motion::Disabled => return, - Motion::Inactive { .. } => VecDeque::from_iter([0.0; u8::MAX as usize + 1]), + Motion::Inactive { .. } => InputQueue::default(), Motion::Active { ref mut motion_inputs, .. - } => motion_inputs.zoom_inputs_mut().drain(..).collect(), + } => InputQueue(motion_inputs.zoom_inputs_mut().0.drain(..).collect()), }; self.motion = Motion::Active { anchor, @@ -149,16 +145,10 @@ impl EditorCam { match motion_inputs { MotionInputs::OrbitZoom { ref mut movement, .. - } => { - movement.push_front(screenspace_input); - movement.truncate(u8::MAX as usize + 1) - } + } => movement.process_input(screenspace_input, self.smoothness.orbit), MotionInputs::PanZoom { ref mut movement, .. - } => { - movement.push_front(screenspace_input); - movement.truncate(u8::MAX as usize + 1) - } + } => movement.process_input(screenspace_input, self.smoothness.pan), MotionInputs::Zoom { .. } => (), // When in zoom-only, we ignore pan and zoom } } @@ -166,10 +156,9 @@ impl EditorCam { pub fn send_zoom(&mut self, zoom_amount: f32) { if let Motion::Active { motion_inputs, .. } = &mut self.motion { - motion_inputs.zoom_inputs_mut().push_front(zoom_amount); motion_inputs .zoom_inputs_mut() - .truncate(u8::MAX as usize + 1); + .process_input(zoom_amount, self.smoothness.zoom) } } @@ -184,15 +173,16 @@ impl EditorCam { } => match motion_inputs { MotionInputs::OrbitZoom { .. } => Velocity::Orbit { anchor, - velocity: motion_inputs.orbit_velocity(self.momentum.smoothness), + velocity: motion_inputs.approx_orbit_velocity(self.momentum.smoothness.orbit), }, MotionInputs::PanZoom { .. } => Velocity::Pan { anchor, - velocity: motion_inputs.pan_velocity(self.momentum.smoothness), + velocity: motion_inputs.approx_pan_velocity(self.momentum.smoothness.pan), }, MotionInputs::Zoom { .. } => Velocity::None, }, }; + dbg!("done"); self.motion = Motion::Inactive { velocity }; } @@ -237,9 +227,9 @@ impl EditorCam { motion_inputs, } => ( anchor, - motion_inputs.orbit_velocity(self.smoothness), - motion_inputs.pan_velocity(self.smoothness), - motion_inputs.zoom_velocity(self.smoothness), + motion_inputs.smooth_orbit_velocity(), + motion_inputs.smooth_pan_velocity(), + motion_inputs.smooth_zoom_velocity(self.smoothness), ), }; @@ -585,24 +575,112 @@ impl From<&MotionInputs> for MotionKind { } } -static FPS: OnceLock> = OnceLock::new(); - -pub(crate) fn update_fps(diagnostics: Res) { - let Ok(mut fps_lock) = FPS.get_or_init(RwLock::default).try_write() else { - return; - }; +/// A smoothed queue of inputs over time. +/// +/// Useful for smoothing to query "what was the average input over the last N milliseconds?". This +/// does some important bookkeeping to ensure samples are not over or under sampled. This means the +/// queue has very useful properties: +/// +/// 1. The smoothing can change over time, useful for sampling over changing framerates. +/// 2. The sum of smoothed and unsmoothed inputs will be equal despite (1). This is useful because +/// you can smooth something like pointer motions, and the smoothed output will arrive at the +/// same destination as the unsmoothed input without drifting. +#[derive(Debug, Clone, Reflect, Default)] +pub struct InputQueue(VecDeque>); - *fps_lock = diagnostics - .get(FrameTimeDiagnosticsPlugin::FPS) - .and_then(|diag| diag.average()) - .unwrap_or(60.0) as f32; +#[derive(Debug, Clone, Reflect)] +struct InputStreamEntry { + /// The time the sample was added and smoothed value computed. + time: Instant, + /// The input sample recorded at this time. + sample: T, + /// How much of this entry is available to be consumed, from `0.0` to `1.0`. This is required to + /// ensure that smoothing does not over or under sample any entries as the size of the sampling + /// window changes. This value should always be zero by the time a sample exits the queue. + fraction_remaining: f32, + /// Because we need to do bookkeeping to ensure no samples are under or over sampled, we compute + /// the smoothed value at the same time a sample is inserted. Because consumers of this will + /// want to read the smoothed samples multiple times, we do the computation eagerly so the input + /// stream is always in a valid state, and the act of a user reading a sample multiple times + /// does not change the value they get. + smoothed_value: T, } -fn read_fps() -> f32 { - FPS.get() - .and_then(|f| f.try_read().ok()) - .map(|d| *d) - .unwrap_or(60.0) +impl + AddAssign + Mul> InputQueue { + const MAX_EVENTS: usize = 128; + + /// Add an input sample to the queue, and compute the smoothed value. + /// + /// The smoothing must be computed at the time a sample is added to ensure no samples are over + /// or under sampled in the smoothing process. + pub fn process_input(&mut self, new_input: T, smoothing: Duration) { + let now = Instant::now(); + let queue = &mut self.0; + + // Compute the expected sampling window end index + let window_size = queue + .iter() + .enumerate() + .find(|(_i, entry)| now.duration_since(entry.time) > smoothing) + .map(|(i, _)| i) // `find` breaks *after* we fail, so we don't need to add one + .unwrap_or(0) + + 1; // Add one to account for the new sample being added + + let range_end = (window_size - 1).clamp(0, queue.len()); + + // Compute the smoothed value by sampling over the desired window + let target_fraction = 1.0 / window_size as f32; + let mut smoothed_value = new_input * target_fraction; + for entry in queue.range_mut(..range_end) { + // Only consume what is left of a sample, to prevent oversampling + let this_fraction = entry.fraction_remaining.min(target_fraction); + smoothed_value += entry.sample * this_fraction; + entry.fraction_remaining = (entry.fraction_remaining - this_fraction).max(0.0); + } + + // To prevent under sampling, we also need to look at entries older than the window, and add + // those to the smoothed value, to catch up. This happens when the window shrinks, or there + // is a pause in rendering and it needs to catch up. + for old_entry in queue + .range_mut(range_end..) + .filter(|e| e.fraction_remaining > 0.0) + { + smoothed_value += old_entry.sample * old_entry.fraction_remaining; + old_entry.fraction_remaining = 0.0; + } + + queue.truncate(Self::MAX_EVENTS - 1); + queue.push_front(InputStreamEntry { + time: now, + sample: new_input, + fraction_remaining: 1.0 - target_fraction, + smoothed_value, + }) + } + + pub fn latest_smoothed(&self) -> Option { + self.0.front().map(|entry| entry.smoothed_value) + } + + pub fn unsmoothed_samples(&self) -> impl Iterator + '_ { + self.0.iter().map(|entry| (entry.time, entry.sample)) + } + + pub fn approx_smoothed(&self, smoothness: Duration, mut modifier: impl FnMut(&mut T)) -> T { + let now = Instant::now(); + let n_elements = &mut 0; + self.unsmoothed_samples() + .filter(|(time, _)| now.duration_since(*time) < smoothness) + .map(|(_, value)| { + *n_elements += 1; + let mut value = value; + modifier(&mut value); + value + }) + .reduce(|acc, v| acc + v) + .unwrap_or_default() + * (1.0 / *n_elements as f32) + } } #[derive(Debug, Clone, Reflect)] @@ -610,21 +688,21 @@ pub enum MotionInputs { /// The camera can orbit and zoom OrbitZoom { /// A queue of screenspace orbiting inputs; usually the mouse drag vector. - movement: VecDeque, + movement: InputQueue, /// A queue of zoom inputs. - zoom_inputs: VecDeque, + zoom_inputs: InputQueue, }, /// The camera can pan and zoom PanZoom { /// A queue of screenspace panning inputs; usually the mouse drag vector. - movement: VecDeque, + movement: InputQueue, /// A queue of zoom inputs. - zoom_inputs: VecDeque, + zoom_inputs: InputQueue, }, /// The camera can only zoom Zoom { /// A queue of zoom inputs. - zoom_inputs: VecDeque, + zoom_inputs: InputQueue, }, } @@ -633,27 +711,63 @@ impl MotionInputs { self.into() } - pub fn orbit_velocity(&self, smoothness: Smoothness) -> DVec2 { + pub fn smooth_orbit_velocity(&self) -> DVec2 { if let Self::OrbitZoom { movement, .. } = self { - let smoothness = (smoothness.orbit.as_secs_f32() * read_fps()) as usize; - let n_elements = movement.len().min(smoothness + 1); - movement.iter().take(n_elements).sum::().as_dvec2() / n_elements as f64 + movement.latest_smoothed().unwrap_or(Vec2::ZERO).as_dvec2() } else { DVec2::ZERO } } - pub fn pan_velocity(&self, smoothness: Smoothness) -> DVec2 { + pub fn approx_orbit_velocity(&self, smoothness: Duration) -> DVec2 { + if let Self::OrbitZoom { movement, .. } = self { + let velocity = movement.approx_smoothed(smoothness, |_| {}).as_dvec2(); + if !velocity.is_finite() { + DVec2::ZERO + } else { + velocity + } + } else { + DVec2::ZERO + } + } + + pub fn smooth_pan_velocity(&self) -> DVec2 { if let Self::PanZoom { movement, .. } = self { - let smoothness = (smoothness.pan.as_secs_f32() * read_fps()) as usize; - let n_elements = movement.len().min(smoothness + 1); - movement.iter().take(n_elements).sum::().as_dvec2() / n_elements as f64 + let value = movement.latest_smoothed().unwrap_or(Vec2::ZERO).as_dvec2(); + if value.is_finite() { + value + } else { + DVec2::ZERO + } } else { DVec2::ZERO } } - pub fn zoom_inputs(&self) -> &VecDeque { + pub fn approx_pan_velocity(&self, smoothness: Duration) -> DVec2 { + if let Self::PanZoom { movement, .. } = self { + let velocity = movement.approx_smoothed(smoothness, |_| {}).as_dvec2(); + if !velocity.is_finite() { + DVec2::ZERO + } else { + velocity + } + } else { + DVec2::ZERO + } + } + + pub fn smooth_zoom_velocity(&self, smoothness: Smoothness) -> f64 { + let velocity = self.zoom_inputs().approx_smoothed(smoothness.zoom, |_| {}) as f64; + if !velocity.is_finite() { + 0.0 + } else { + velocity + } + } + + pub fn zoom_inputs(&self) -> &InputQueue { match self { MotionInputs::OrbitZoom { zoom_inputs, .. } => zoom_inputs, MotionInputs::PanZoom { zoom_inputs, .. } => zoom_inputs, @@ -661,7 +775,7 @@ impl MotionInputs { } } - pub fn zoom_inputs_mut(&mut self) -> &mut VecDeque { + pub fn zoom_inputs_mut(&mut self) -> &mut InputQueue { match self { MotionInputs::OrbitZoom { zoom_inputs, .. } => zoom_inputs, MotionInputs::PanZoom { zoom_inputs, .. } => zoom_inputs, @@ -669,32 +783,16 @@ impl MotionInputs { } } - pub fn zoom_velocity(&self, smoothness: Smoothness) -> f64 { - let zoom_inputs = self.zoom_inputs(); - let smoothness = (smoothness.zoom.as_secs_f32() * read_fps()) as usize; - let n_elements = zoom_inputs.len().min(smoothness + 1); - let velocity = zoom_inputs.iter().take(n_elements).sum::() as f64 / n_elements as f64; - if !velocity.is_finite() { - 0.0 - } else { - velocity - } - } - pub fn zoom_velocity_abs(&self, smoothness: Smoothness) -> f64 { let zoom_inputs = match self { MotionInputs::OrbitZoom { zoom_inputs, .. } => zoom_inputs, MotionInputs::PanZoom { zoom_inputs, .. } => zoom_inputs, MotionInputs::Zoom { zoom_inputs } => zoom_inputs, }; - let smoothness = (smoothness.zoom.as_secs_f32() * read_fps()) as usize; - let n_elements = zoom_inputs.len().min(smoothness + 1); - let velocity = zoom_inputs - .iter() - .take(n_elements) - .map(|input| input.abs()) - .sum::() as f64 - / n_elements as f64; + + let velocity = zoom_inputs.approx_smoothed(smoothness.zoom, |v| { + *v = v.abs(); + }) as f64; if !velocity.is_finite() { 0.0 } else { diff --git a/src/dolly_zoom.rs b/src/dolly_zoom.rs index c228256..f2efaa6 100644 --- a/src/dolly_zoom.rs +++ b/src/dolly_zoom.rs @@ -35,10 +35,10 @@ pub enum DollyZoomProjection { impl DollyZoom { fn update(mut cameras: Query<(&mut Self, &mut EditorCam, &mut Projection, &mut Transform)>) { - for (mut dolly, mut editor_cam, mut proj, mut transform) in &mut cameras { + for (mut dolly, mut editor_cam, mut current_projection, mut transform) in &mut cameras { let forward = transform.forward(); match dolly.target_projection { - DollyZoomProjection::Perspective => match &mut *proj { + DollyZoomProjection::Perspective => match &mut *current_projection { Projection::Perspective(perspective) => { let dolly_movement = Self::animated_offset(0.0, dolly.dist_to_target, dolly.animation_speed); @@ -63,7 +63,7 @@ impl DollyZoom { // }); } }, - DollyZoomProjection::Orthographic => match &mut *proj { + DollyZoomProjection::Orthographic => match &mut *current_projection { Projection::Orthographic(_) => continue, Projection::Perspective(perspective) => { let dolly_movement = Self::animated_offset( @@ -77,12 +77,13 @@ impl DollyZoom { transform.translation += forward * dolly_movement; if (dolly.dist_to_target - dolly.maximum_dolly_pull).abs() < 0.01 { - *proj = Projection::Orthographic(OrthographicProjection { - near: dolly.near, - far: dolly.far, - scale: editor_cam.latest_depth as f32, // compute this gooder? - ..Default::default() - }); + *current_projection = + Projection::Orthographic(OrthographicProjection { + near: dolly.near, + far: dolly.far, + scale: editor_cam.latest_depth as f32, // compute this gooder? + ..Default::default() + }); editor_cam.latest_depth += dolly.dist_to_target as f64; transform.translation += forward * dolly.dist_to_target; dolly.dist_to_target = 0.0; diff --git a/src/input.rs b/src/input.rs index 30e6dd6..8626d7e 100644 --- a/src/input.rs +++ b/src/input.rs @@ -239,7 +239,7 @@ impl EditorCamInputEvent { let zoom_amount = match pointer { // FIXME: account for different scroll units // TODO: add pinch zoom support - PointerId::Mouse => mouse_wheel.read().map(|mw| mw.y).sum::() * 2.0, + PointerId::Mouse => mouse_wheel.read().map(|mw| mw.y).sum::(), _ => 0.0, }; diff --git a/src/plugin.rs b/src/plugin.rs index fa306d1..15bec61 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{diagnostic::FrameTimeDiagnosticsPlugin, prelude::*}; use bevy_picking_core::PickSet; use crate::{ @@ -18,7 +18,6 @@ impl Plugin for EditorCamPlugin { .add_systems( PreUpdate, ( - crate::cam_component::update_fps, crate::input::default_camera_inputs, EditorCamInputEvent::receive_events, EditorCamInputEvent::update_moves, @@ -28,4 +27,10 @@ impl Plugin for EditorCamPlugin { .after(PickSet::Last), ); } + + fn finish(&self, app: &mut App) { + if !app.is_plugin_added::() { + app.add_plugins(FrameTimeDiagnosticsPlugin); + } + } }