-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Smooth Damp #2001
Smooth Damp #2001
Changes from 23 commits
8b8ad09
a79e3cc
c387dba
3b31005
565f556
16ba211
2f3b0cb
830fef9
1485281
b74ae4f
f685e66
a060a0d
e54efb3
5755ffd
ece1667
19ed7e4
2b152b2
2a7211f
745b368
687e47a
24b0b2f
3397f26
4331def
e4d42ad
71ab208
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,15 @@ | ||
mod face_toward; | ||
mod geometry; | ||
mod smooth; | ||
|
||
pub use face_toward::*; | ||
pub use geometry::*; | ||
pub use glam::*; | ||
pub use smooth::*; | ||
|
||
pub mod prelude { | ||
pub use crate::{ | ||
BVec2, BVec3, BVec4, FaceToward, IVec2, IVec3, IVec4, Mat3, Mat4, Quat, Rect, Size, UVec2, | ||
UVec3, UVec4, Vec2, Vec3, Vec4, | ||
BVec2, BVec3, BVec4, FaceToward, IVec2, IVec3, IVec4, Mat3, Mat4, Quat, Rect, Size, | ||
SmoothDamp, SmoothDampMax, UVec2, UVec3, UVec4, Vec2, Vec3, Vec4, | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
use crate::{Vec2, Vec3}; | ||
|
||
/// Smooths value to a goal using a damped spring. | ||
pub trait SmoothDamp { | ||
/// Smooths value to a goal using a damped spring. | ||
/// | ||
/// `smooth_time` is the expected time to reach the target when at maximum velocity. | ||
/// | ||
/// Returns smoothed value and new velocity. | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// # use bevy_math::prelude::{Vec3, Quat}; | ||
/// # use bevy_math::SmoothDamp; | ||
/// # struct Transform { | ||
/// # translation: Vec3, | ||
/// # rotation: Quat, | ||
/// # scale: Vec3 | ||
/// # } | ||
/// struct SmoothTransform { | ||
/// pub smoothness: f32, | ||
/// pub target: Vec3, | ||
/// velocity: Vec3 | ||
/// } | ||
/// | ||
/// fn smooth_transform_update(dt: f32, transform: &mut Transform, smoother: &mut SmoothTransform) { | ||
/// let (p, v) = Vec3::smooth_damp( | ||
/// transform.translation, | ||
/// smoother.target, | ||
/// smoother.velocity, | ||
/// smoother.smoothness, | ||
/// dt, | ||
/// ); | ||
/// transform.translation = p; | ||
/// smoother.velocity = v; | ||
/// // When destructured assignement will be supported by Rust: | ||
/// // (transform.translation, smoother.velocity) = | ||
/// // Vec3::smooth_damp( | ||
/// // transform.translation, | ||
/// // smoother.target, | ||
/// // smoother.velocity, | ||
/// // smoother.smoothness, | ||
/// // dt, | ||
/// // ); | ||
/// } | ||
/// ``` | ||
fn smooth_damp( | ||
from: Self, | ||
to: Self, | ||
velocity: Self, | ||
smooth_time: f32, | ||
delta_time: f32, | ||
) -> (Self, Self) | ||
where | ||
Self: Sized; | ||
} | ||
|
||
macro_rules! impl_smooth_damp { | ||
($t:ty, $f:ty) => { | ||
impl SmoothDamp for $t { | ||
fn smooth_damp( | ||
from: $t, | ||
to: $t, | ||
velocity: $t, | ||
smooth_time: f32, | ||
delta_time: f32, | ||
) -> ($t, $t) { | ||
let smooth_time = <$f>::max(smooth_time as $f, 0.0001); // ensure smooth_time is positive and non-zero | ||
let delta_time = delta_time as $f; | ||
msklywenn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// from game programming gems 4, chapter 1.10 | ||
let omega = 2.0 / smooth_time; | ||
let x = omega * delta_time; | ||
|
||
// fast and good enough approximation of exp(x) | ||
let exp = 1.0 / (1.0 + x * (1.0 + x * (0.48 + 0.235 * x))); | ||
|
||
let change = from - to; | ||
let temp = (velocity + omega * change) * delta_time; | ||
|
||
( | ||
to + (change + temp) * exp, // position | ||
(velocity - omega * temp) * exp, // velocity | ||
) | ||
} | ||
} | ||
}; | ||
} | ||
|
||
impl_smooth_damp! {f32, f32} | ||
impl_smooth_damp! {f64, f64} | ||
impl_smooth_damp! {Vec2, f32} | ||
impl_smooth_damp! {Vec3, f32} | ||
|
||
/// Smooths value to a goal using a damped spring limited by a maximum speed. | ||
pub trait SmoothDampMax { | ||
/// Smooths value to a goal using a damped spring limited by a maximum speed. | ||
/// | ||
/// `smooth_time` is the expected time to reach the target when at maximum velocity. | ||
/// | ||
/// Returns smoothed value and new velocity. | ||
fn smooth_damp_max( | ||
from: Self, | ||
to: Self, | ||
velocity: Self, | ||
max_speed: f32, | ||
smooth_time: f32, | ||
delta_time: f32, | ||
) -> (Self, Self) | ||
where | ||
Self: Sized; | ||
} | ||
|
||
macro_rules! impl_smooth_damp_max { | ||
($t:ty, $f:ty, $clamp:expr) => { | ||
impl SmoothDampMax for $t { | ||
fn smooth_damp_max( | ||
from: $t, | ||
to: $t, | ||
velocity: $t, | ||
max_speed: f32, | ||
smooth_time: f32, | ||
delta_time: f32, | ||
) -> ($t, $t) { | ||
let max_speed = <$f>::max(max_speed as $f, 0.0001); // ensure max speed is positive and non-zero | ||
msklywenn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let smooth_time = <$f>::max(smooth_time as $f, 0.0001); // ensure smooth_time is positive and non-zero | ||
let delta_time = delta_time as $f; | ||
msklywenn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// from game programming gems 4, chapter 1.10 | ||
let omega = 2.0 / smooth_time; | ||
let x = omega * delta_time; | ||
|
||
// fast and good enough approximation of exp(x) | ||
let exp = 1.0 / (1.0 + x * (1.0 + x * (0.48 + 0.235 * x))); | ||
|
||
let max = max_speed * smooth_time; | ||
let change = from - to; | ||
let change = $clamp(change, max); | ||
let to = from - change; | ||
|
||
let temp = (velocity + omega * change) * delta_time; | ||
|
||
( | ||
to + (change + temp) * exp, // position | ||
(velocity - omega * temp) * exp, // velocity | ||
) | ||
} | ||
} | ||
}; | ||
} | ||
|
||
impl_smooth_damp_max! {f32, f32, |change, max:f32| { f32::clamp(change, -max, max) }} | ||
impl_smooth_damp_max! {f64, f64, |change, max:f64| { f64::clamp(change, -max, max) }} | ||
impl_smooth_damp_max! {Vec2, f32, |change:Vec2, max| { change.clamp_length_max(max) }} | ||
impl_smooth_damp_max! {Vec3, f32, |change:Vec3, max| { change.clamp_length_max(max) }} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should probably write some test cases in a test module There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup. Usually math functions are great candidates for tests. However, I've been scratching my head since the beginning and I have no idea of a good test for this. The function is usually used iteratively... Any suggestions? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO returning structs > return tuples.
Having a struct makes it much clearer what each value is and prevent bugs where someone accidentally switches
.0
and.1
.So having this return something like
would be much clearer and prevent some user-end bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment, rust doesn't support tuple destructuring assignment (rust-lang/rust#71126).
But in the future, I expect users to call the function this way:
(obj.pos, obj.vel) = smooth_damp(obj.pos, target, obj.vel, 0.1, dt);
That's the only reason I wrote it this way.If I were to use a SmoothDamp structure, I would use it as input too, but that's less friendly. It would prevent smoothing the translation of a Transform while keeping the velocity in another component, for example.
The last option would be to make the velocity input &mut, like C/C++/C# version do, but I fear needless lifetime issues and I like the function being pure...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Input parameters via
&mut
are not a good option IMO.I would still argue for the
struct
return value since you can do something like this:But I would leave this decision up to @cart 😄