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

Frame rate independent smoothing #62

Merged
merged 3 commits into from
Feb 26, 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
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);
}
}
Loading