Skip to content

Commit

Permalink
Use smoothed values for momentum
Browse files Browse the repository at this point in the history
  • Loading branch information
aevyrie committed Jan 7, 2024
1 parent 3bccc39 commit 1a7a4cb
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 36 deletions.
30 changes: 19 additions & 11 deletions examples/demo.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
use std::{thread::sleep, time::Duration};
use std::time::Duration;

use bevy::{
core_pipeline::{bloom::BloomSettings, tonemapping::Tonemapping},
core_pipeline::{
bloom::BloomSettings,
experimental::taa::{TemporalAntiAliasBundle, TemporalAntiAliasPlugin},
tonemapping::Tonemapping,
},
pbr::ScreenSpaceAmbientOcclusionSettings,
prelude::*,
};
use bevy_editor_cam::{prelude::*, skybox::SkyboxCamConfig};
use bevy_framepace::FramepacePlugin;

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,
bevy_framepace::FramepacePlugin,
FramepacePlugin,
TemporalAntiAliasPlugin,
EditorCamPlugin,
))
.add_systems(Startup, setup)
Expand Down Expand Up @@ -80,24 +87,25 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..default()
},
BloomSettings::default(),
TemporalAntiAliasBundle::default(),
ScreenSpaceAmbientOcclusionSettings::default(),
EnvironmentMapLight {
diffuse_map: diffuse_map.clone(),
specular_map: specular_map.clone(),
},
EditorCam::new(
// OrbitMode::Constrained(Vec3::Y),
OrbitMode::Free,
OrbitMode::Constrained(Vec3::Y),
// OrbitMode::Free,
Smoothness {
pan: Duration::from_millis(10),
pan: Duration::from_millis(12),
orbit: Duration::from_millis(40),
zoom: Duration::from_millis(40),
zoom: Duration::from_millis(60),
},
Sensitivity::same(1.0),
Momentum {
// These should all be larger than the base smoothness
smoothness: Smoothness {
pan: Duration::from_millis(80),
orbit: Duration::from_millis(100),
pan: Duration::from_millis(40),
orbit: Duration::from_millis(40),
zoom: Duration::from_millis(0),
},
pan: 150,
Expand Down
117 changes: 93 additions & 24 deletions src/cam_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,30 @@ pub struct EditorCam {
pub latest_depth: f64,
}

impl Default for EditorCam {
fn default() -> Self {
EditorCam::new(
OrbitMode::Constrained(Vec3::Y),
Smoothness {
pan: Duration::from_millis(12),
orbit: Duration::from_millis(40),
zoom: Duration::from_millis(60),
},
Sensitivity::same(1.0),
Momentum {
smoothness: Smoothness {
pan: Duration::from_millis(40),
orbit: Duration::from_millis(40),
zoom: Duration::from_millis(0),
},
pan: 150,
orbit: 100,
},
2.0,
)
}
}

impl EditorCam {
pub fn new(
orbit: OrbitMode,
Expand Down Expand Up @@ -173,11 +197,11 @@ impl EditorCam {
} => match motion_inputs {
MotionInputs::OrbitZoom { .. } => Velocity::Orbit {
anchor,
velocity: motion_inputs.approx_orbit_velocity(self.momentum.smoothness.orbit),
velocity: motion_inputs.orbit_momentum(self.momentum.smoothness.orbit),
},
MotionInputs::PanZoom { .. } => Velocity::Pan {
anchor,
velocity: motion_inputs.approx_pan_velocity(self.momentum.smoothness.pan),
velocity: motion_inputs.pan_momentum(self.momentum.smoothness.pan),
},
MotionInputs::Zoom { .. } => Velocity::None,
},
Expand Down Expand Up @@ -228,7 +252,7 @@ impl EditorCam {
anchor,
motion_inputs.smooth_orbit_velocity(),
motion_inputs.smooth_pan_velocity(),
motion_inputs.smooth_zoom_velocity(self.smoothness),
motion_inputs.smooth_zoom_velocity(),
),
};

Expand Down Expand Up @@ -584,7 +608,7 @@ impl From<&MotionInputs> for MotionKind {
/// 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)]
#[derive(Debug, Clone, Reflect)]
pub struct InputQueue<T>(VecDeque<InputStreamEntry<T>>);

#[derive(Debug, Clone, Reflect)]
Expand All @@ -605,8 +629,27 @@ struct InputStreamEntry<T> {
smoothed_value: T,
}

impl<T: Copy + Default + Add<Output = T> + AddAssign<T> + Mul<f32, Output = T>> Default
for InputQueue<T>
{
fn default() -> Self {
let start = Instant::now();
let interval = Duration::from_secs_f32(1.0 / 60.0);
let mut queue = VecDeque::default();
for i in 0..Self::MAX_EVENTS {
queue.push_front(InputStreamEntry {
time: start - interval.mul_f32(i as f32),
sample: T::default(),
fraction_remaining: 0.0,
smoothed_value: T::default(),
})
}
Self(queue)
}
}

impl<T: Copy + Default + Add<Output = T> + AddAssign<T> + Mul<f32, Output = T>> InputQueue<T> {
const MAX_EVENTS: usize = 128;
const MAX_EVENTS: usize = 256;

/// Add an input sample to the queue, and compute the smoothed value.
///
Expand Down Expand Up @@ -658,17 +701,38 @@ impl<T: Copy + Default + Add<Output = T> + AddAssign<T> + Mul<f32, Output = T>>
}

pub fn latest_smoothed(&self) -> Option<T> {
self.0.front().map(|entry| entry.smoothed_value)
self.iter_smoothed().next().map(|(_, val)| val)
}

pub fn iter_smoothed(&self) -> impl Iterator<Item = (Instant, T)> + '_ {
self.0
.iter()
.map(|entry| (entry.time, entry.smoothed_value))
}

pub fn unsmoothed_samples(&self) -> impl Iterator<Item = (Instant, T)> + '_ {
pub fn iter_unsmoothed(&self) -> impl Iterator<Item = (Instant, T)> + '_ {
self.0.iter().map(|entry| (entry.time, entry.sample))
}

pub fn average_smoothed_value(&self, window: Duration) -> T {
let now = Instant::now();
let mut count = 0;
let sum = self
.iter_smoothed()
.filter(|(t, _)| now.duration_since(*t) < window)
.map(|(_, smoothed_value)| smoothed_value)
.reduce(|acc, v| {
count += 1;
acc + v
})
.unwrap_or_default();
sum * (1.0 / count as f32)
}

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()
self.iter_unsmoothed()
.filter(|(time, _)| now.duration_since(*time) < smoothness)
.map(|(_, value)| {
*n_elements += 1;
Expand Down Expand Up @@ -712,19 +776,11 @@ impl MotionInputs {

pub fn smooth_orbit_velocity(&self) -> DVec2 {
if let Self::OrbitZoom { movement, .. } = self {
movement.latest_smoothed().unwrap_or(Vec2::ZERO).as_dvec2()
} else {
DVec2::ZERO
}
}

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
let value = movement.latest_smoothed().unwrap_or(Vec2::ZERO).as_dvec2();
if value.is_finite() {
value
} else {
velocity
DVec2::ZERO
}
} else {
DVec2::ZERO
Expand All @@ -744,9 +800,22 @@ impl MotionInputs {
}
}

pub fn approx_pan_velocity(&self, smoothness: Duration) -> DVec2 {
pub fn orbit_momentum(&self, window: Duration) -> DVec2 {
if let Self::OrbitZoom { movement, .. } = self {
let velocity = movement.average_smoothed_value(window).as_dvec2();
if !velocity.is_finite() {
DVec2::ZERO
} else {
velocity
}
} else {
DVec2::ZERO
}
}

pub fn pan_momentum(&self, window: Duration) -> DVec2 {
if let Self::PanZoom { movement, .. } = self {
let velocity = movement.approx_smoothed(smoothness, |_| {}).as_dvec2();
let velocity = movement.average_smoothed_value(window).as_dvec2();
if !velocity.is_finite() {
DVec2::ZERO
} else {
Expand All @@ -757,8 +826,8 @@ impl MotionInputs {
}
}

pub fn smooth_zoom_velocity(&self, smoothness: Smoothness) -> f64 {
let velocity = self.zoom_inputs().approx_smoothed(smoothness.zoom, |_| {}) as f64;
pub fn smooth_zoom_velocity(&self) -> f64 {
let velocity = self.zoom_inputs().latest_smoothed().unwrap_or(0.0) as f64;
if !velocity.is_finite() {
0.0
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ impl EditorCamInputEvent {

let zoom_amount = match pointer {
// FIXME: account for different scroll units
// TODO: add pinch zoom support
// TODO: add pinch zoom support, probably in mod_picking
PointerId::Mouse => mouse_wheel.read().map(|mw| mw.y).sum::<f32>(),
_ => 0.0,
};
Expand Down

0 comments on commit 1a7a4cb

Please sign in to comment.