Skip to content

Commit

Permalink
Frame rate independent smoothing (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
Plonq authored Feb 26, 2024
1 parent a0357f2 commit de9305a
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 21 deletions.
11 changes: 8 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,11 @@ impl Default for PanOrbitCamera {
is_upside_down: false,
allow_upside_down: false,
orbit_sensitivity: 1.0,
orbit_smoothness: 0.8,
orbit_smoothness: 0.1,
pan_sensitivity: 1.0,
pan_smoothness: 0.6,
pan_smoothness: 0.02,
zoom_sensitivity: 1.0,
zoom_smoothness: 0.8,
zoom_smoothness: 0.1,
button_orbit: MouseButton::Left,
button_pan: MouseButton::Right,
modifier_orbit: None,
Expand Down Expand Up @@ -401,6 +401,7 @@ fn pan_orbit_camera(
mouse_key_tracker: Res<MouseKeyTracker>,
touch_tracker: Res<TouchTracker>,
mut orbit_cameras: Query<(Entity, &mut PanOrbitCamera, &mut Transform, &mut Projection)>,
time: Res<Time>,
) {
for (entity, mut pan_orbit, mut transform, mut projection) in orbit_cameras.iter_mut() {
// Closures that apply limits to the alpha, beta, and zoom values
Expand Down Expand Up @@ -618,21 +619,25 @@ fn pan_orbit_camera(
alpha,
pan_orbit.target_alpha,
pan_orbit.orbit_smoothness,
time.delta_seconds(),
);
let new_beta = util::lerp_and_snap_f32(
beta,
pan_orbit.target_beta,
pan_orbit.orbit_smoothness,
time.delta_seconds(),
);
let new_radius = util::lerp_and_snap_f32(
radius,
pan_orbit.target_radius,
pan_orbit.zoom_smoothness,
time.delta_seconds(),
);
let new_focus = util::lerp_and_snap_vec3(
pan_orbit.focus,
pan_orbit.target_focus,
pan_orbit.pan_smoothness,
time.delta_seconds(),
);

util::update_orbit_transform(
Expand Down
38 changes: 20 additions & 18 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@ pub fn approx_equal(a: f32, b: f32) -> bool {
(a - b).abs() < EPSILON
}

pub fn lerp_and_snap_f32(from: f32, to: f32, smoothness: f32) -> f32 {
let t = 1.0 - smoothness;
let mut new_value = from.lerp(to, t);
pub fn lerp_and_snap_f32(from: f32, to: f32, smoothness: f32, dt: f32) -> f32 {
let t = smoothness.powi(7);
let mut new_value = from.lerp(to, 1.0 - t.powf(dt));
if smoothness < 1.0 && approx_equal(new_value, to) {
new_value = to;
}
new_value
}

pub fn lerp_and_snap_vec3(from: Vec3, to: Vec3, smoothness: f32) -> Vec3 {
let t = 1.0 - smoothness;
let mut new_value = from.lerp(to, t);
pub fn lerp_and_snap_vec3(from: Vec3, to: Vec3, smoothness: f32, dt: f32) -> Vec3 {
let t = smoothness.powi(7);
let mut new_value = from.lerp(to, 1.0 - t.powf(dt));
if smoothness < 1.0 && approx_equal((new_value - to).length(), 0.0) {
new_value.x = to.x;
}
Expand Down Expand Up @@ -142,24 +142,25 @@ mod lerp_and_snap_f32_tests {

#[test]
fn lerps_when_output_outside_snap_threshold() {
let out = lerp_and_snap_f32(1.0, 2.0, 0.5);
assert_eq!(out, 1.5);
let out = lerp_and_snap_f32(1.0, 2.0, 0.5, 1.0);
// Due to the frame rate independence, this value is not easily predictable
assert_eq!(out, 1.9921875);
}

#[test]
fn snaps_to_target_when_inside_threshold() {
let out = lerp_and_snap_f32(1.9991, 2.0, 0.5);
let out = lerp_and_snap_f32(1.9991, 2.0, 0.5, 1.0);
assert_eq!(out, 2.0);
let out = lerp_and_snap_f32(1.9991, 2.0, 0.1);
let out = lerp_and_snap_f32(1.9991, 2.0, 0.1, 1.0);
assert_eq!(out, 2.0);
let out = lerp_and_snap_f32(1.9991, 2.0, 0.9);
let out = lerp_and_snap_f32(1.9991, 2.0, 0.9, 1.0);
assert_eq!(out, 2.0);
}

#[test]
fn does_not_snap_if_smoothness_is_one() {
// Smoothness of one results in the value not changing, so it doesn't make sense to snap
let out = lerp_and_snap_f32(1.9991, 2.0, 1.0);
let out = lerp_and_snap_f32(1.9991, 2.0, 1.0, 1.0);
assert_eq!(out, 1.9991);
}
}
Expand All @@ -170,24 +171,25 @@ mod lerp_and_snap_vec3_tests {

#[test]
fn lerps_when_output_outside_snap_threshold() {
let out = lerp_and_snap_vec3(Vec3::ZERO, Vec3::X, 0.5);
assert_eq!(out, Vec3::X * 0.5);
let out = lerp_and_snap_vec3(Vec3::ZERO, Vec3::X, 0.5, 1.0);
// Due to the frame rate independence, this value is not easily predictable
assert_eq!(out, Vec3::new(0.9921875, 0.0, 0.0));
}

#[test]
fn snaps_to_target_when_inside_threshold() {
let out = lerp_and_snap_vec3(Vec3::X * 0.9991, Vec3::X, 0.5);
let out = lerp_and_snap_vec3(Vec3::X * 0.9991, Vec3::X, 0.5, 1.0);
assert_eq!(out, Vec3::X);
let out = lerp_and_snap_vec3(Vec3::X * 0.9991, Vec3::X, 0.1);
let out = lerp_and_snap_vec3(Vec3::X * 0.9991, Vec3::X, 0.1, 1.0);
assert_eq!(out, Vec3::X);
let out = lerp_and_snap_vec3(Vec3::X * 0.9991, Vec3::X, 0.9);
let out = lerp_and_snap_vec3(Vec3::X * 0.9991, Vec3::X, 0.9, 1.0);
assert_eq!(out, Vec3::X);
}

#[test]
fn does_not_snap_if_smoothness_is_one() {
// Smoothness of one results in the value not changing, so it doesn't make sense to snap
let out = lerp_and_snap_vec3(Vec3::X * 0.9991, Vec3::X, 1.0);
let out = lerp_and_snap_vec3(Vec3::X * 0.9991, Vec3::X, 1.0, 1.0);
assert_eq!(out, Vec3::X * 0.9991);
}
}

0 comments on commit de9305a

Please sign in to comment.