I am implementing a system that takes care of dragging and dropping inventory items and want a way to return items to their old place if the desired place is invalid. I found a good method for that use case in the smooth_nudge
method.
Bevy provides the
StableInterpolate Trait which provides the smooth_nudge
method to interpolate the position of two Transforms.
I want to know what the best way is to check if the desired target Transform is reached.
A naive implementation could simply compare the position of one transformation with the target transformation, but due to floating arithmetic this approach does not work very well.
#[derive(Component)]
struct DragReturn {
target_position: Vec3,
}
fn move_invalid_drag_to_old_pos(
mut commands: Commands,
mut query: Query<(Entity, &mut Transform, &DragReturn)>,
time: Res<Time>,
) {
for (e, mut t, dr) in &mut query {
// reached position
if t.translation == dr.target_position {
println!("remove DragReturn");
commands.entity(e).remove::<DragReturn>();
}
t.translation
.smooth_nudge(&dr.target_position, 2.0, time.delta_secs());
}
}
Also, one could calculate the distance between the two positions and consider the objects to have arrived at the target if the distance is very small. My question now is if there is a standardized/best practice Bevy way to check this?
Under the hood, smooth_nudge uses exp
to adjust the value passed to interpolate_stable
. As such, you will never truly reach the endpoint, because that would require 1.0 - e ^ (-decay_rate * delta)
to equal 1.0
, and e^x
never equals zero, only approaces zero at infinity.
But, theory isn't reality, and eventually you'll eventually hit the minimum values you can represent with f32 or f64. For practical purposes you can choose when the value is "close enough" to the final one and snap to it. For your example, something like the following would be appropriate. There's no one-size fits all value, and it all has to do with your use case to define what's "close enough".
// using length_squared instead of length for performance
if (t.translation - dr.target_position).length_squared() < 0.001 {
println!("snapping to end");
t.translation = dr.target_position;
println!("remove DragReturn");
commands.entity(e).remove::<DragReturn>();
}
If you want something that guarantees being "done" at a certain point, you should use slerp
, lerp
, or interpolate_stable
, which all guarantee that the returned value will be the target value when t
equals 1.0
.