Skip to content
Snippets Groups Projects
Unverified Commit dd6ac741 authored by Jerome Humbert's avatar Jerome Humbert Committed by GitHub
Browse files

Add `assert_approx_eq!()` testing macro (#66)

Clarify testing code and potential assertions message with the use of a
new `assert_approx_eq!()` macro for `f32` equality check with a
tolerance. The macro leverages `abs_diff_eq()` but produces a better
assertion message in case of failure. It also allows skipping the
tolerance parameter to use the default of `1e-5`, which is the
"standard" tolerance to use for progress and other small-ish values that
are expected to be equal but might be slightly off due to rounding
errors.

This change ignores the complications of testing for floating-point
equality in a generic way, which is too complex, and instead restrict
the usage to values like progress (range [0:1]) and other small position
values around the origin.
parent 15c5bdbd
No related branches found
No related tags found
No related merge requests found
use bevy::prelude::*;
use bevy_inspector_egui::WorldInspectorPlugin;
use bevy_tweening::{lens::*, *};
use std::time::Duration;
const NORMAL_COLOR: Color = Color::rgba(162. / 255., 226. / 255., 95. / 255., 1.);
const HOVER_COLOR: Color = Color::AZURE;
const CLICK_COLOR: Color = Color::ALICE_BLUE;
const TEXT_COLOR: Color = Color::rgba(83. / 255., 163. / 255., 130. / 255., 1.);
const INIT_TRANSITION_DONE: u64 = 1;
/// The menu in this example has two set of animations:
/// one for appearance, one for interaction. Interaction animations
/// are only enabled after appearance animations finished.
///
/// The logic is handled as:
/// 1. Appearance animations send a `TweenComplete` event with `INIT_TRANSITION_DONE`
/// 2. The `enable_interaction_after_initial_animation` system adds a label component
/// `InitTransitionDone` to any button component which completed its appearance animation,
/// to mark it as active.
/// 3. The `interaction` system only queries buttons with a `InitTransitionDone` marker.
fn main() {
App::default()
.insert_resource(WindowDescriptor {
title: "Menu".to_string(),
width: 800.,
height: 400.,
present_mode: bevy::window::PresentMode::Fifo, // vsync
..default()
})
.add_plugins(DefaultPlugins)
.add_system(bevy::window::close_on_esc)
.add_system(interaction)
.add_system(enable_interaction_after_initial_animation)
.add_plugin(TweeningPlugin)
.add_plugin(WorldInspectorPlugin::new())
.add_startup_system(setup)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn_bundle(Camera2dBundle::default());
let font = asset_server.load("fonts/FiraMono-Regular.ttf");
commands
.spawn_bundle(NodeBundle {
style: Style {
position_type: PositionType::Absolute,
position: UiRect::all(Val::Px(0.)),
margin: UiRect::all(Val::Px(16.)),
padding: UiRect::all(Val::Px(16.)),
flex_direction: FlexDirection::ColumnReverse,
align_content: AlignContent::Center,
align_items: AlignItems::Center,
align_self: AlignSelf::Center,
justify_content: JustifyContent::Center,
..default()
},
color: UiColor(Color::NONE),
..default()
})
.insert(Name::new("menu"))
.with_children(|container| {
let mut start_time_ms = 0;
for (text, label) in [
("Continue", ButtonLabel::Continue),
("New Game", ButtonLabel::NewGame),
("Settings", ButtonLabel::Settings),
("Quit", ButtonLabel::Quit),
] {
let tween_scale = Tween::new(
EaseFunction::BounceOut,
Duration::from_secs(2),
TransformScaleLens {
start: Vec3::splat(0.01),
end: Vec3::ONE,
},
)
.with_completed_event(INIT_TRANSITION_DONE);
let animator = if start_time_ms > 0 {
let delay = Delay::new(Duration::from_millis(start_time_ms));
Animator::new(delay.then(tween_scale))
} else {
Animator::new(tween_scale)
};
start_time_ms += 500;
container
.spawn_bundle(ButtonBundle {
node: Node {
size: Vec2::new(300., 80.),
},
style: Style {
min_size: Size::new(Val::Px(300.), Val::Px(80.)),
margin: UiRect::all(Val::Px(8.)),
padding: UiRect::all(Val::Px(8.)),
align_content: AlignContent::Center,
align_items: AlignItems::Center,
align_self: AlignSelf::Center,
justify_content: JustifyContent::Center,
..default()
},
color: UiColor(NORMAL_COLOR),
transform: Transform::from_scale(Vec3::splat(0.01)),
..default()
})
.insert(Name::new(format!("button:{}", text)))
.insert(animator)
.insert(label)
.with_children(|parent| {
parent.spawn_bundle(TextBundle {
text: Text::from_section(
text.to_string(),
TextStyle {
font: font.clone(),
font_size: 48.0,
color: TEXT_COLOR,
},
)
.with_alignment(TextAlignment {
vertical: VerticalAlign::Center,
horizontal: HorizontalAlign::Center,
}),
..default()
});
});
}
});
}
fn enable_interaction_after_initial_animation(
mut commands: Commands,
mut reader: EventReader<TweenCompleted>,
) {
for event in reader.iter() {
if event.user_data == INIT_TRANSITION_DONE {
commands.entity(event.entity).insert(InitTransitionDone);
}
}
}
#[derive(Component)]
struct InitTransitionDone;
#[derive(Component, Clone, Copy)]
enum ButtonLabel {
Continue,
NewGame,
Settings,
Quit,
}
fn interaction(
mut interaction_query: Query<
(
&mut Animator<Transform>,
&Transform,
&Interaction,
&mut UiColor,
&ButtonLabel,
),
(Changed<Interaction>, With<InitTransitionDone>),
>,
) {
for (mut animator, transform, interaction, mut color, button_label) in &mut interaction_query {
match *interaction {
Interaction::Clicked => {
*color = CLICK_COLOR.into();
match button_label {
ButtonLabel::Continue => {
println!("Continue clicked");
}
ButtonLabel::NewGame => {
println!("NewGame clicked");
}
ButtonLabel::Settings => {
println!("Settings clicked");
}
ButtonLabel::Quit => {
println!("Quit clicked");
}
}
}
Interaction::Hovered => {
*color = HOVER_COLOR.into();
animator.set_tweenable(Tween::new(
EaseFunction::QuadraticIn,
Duration::from_millis(200),
TransformScaleLens {
start: Vec3::ONE,
end: Vec3::splat(1.1),
},
));
}
Interaction::None => {
*color = NORMAL_COLOR.into();
let start_scale = transform.scale;
animator.set_tweenable(Tween::new(
EaseFunction::QuadraticIn,
Duration::from_millis(200),
TransformScaleLens {
start: start_scale,
end: Vec3::ONE,
},
));
}
}
}
}
use bevy::prelude::*;
use bevy_inspector_egui::WorldInspectorPlugin;
use bevy_tweening::{lens::*, *};
use std::time::Duration;
const NORMAL_COLOR: Color = Color::rgba(162. / 255., 226. / 255., 95. / 255., 1.);
const HOVER_COLOR: Color = Color::AZURE;
const CLICK_COLOR: Color = Color::ALICE_BLUE;
const TEXT_COLOR: Color = Color::rgba(83. / 255., 163. / 255., 130. / 255., 1.);
const INIT_TRANSITION_DONE: u64 = 1;
/// The menu in this example has two set of animations:
/// one for appearance, one for interaction. Interaction animations
/// are only enabled after appearance animations finished.
///
/// The logic is handled as:
/// 1. Appearance animations send a `TweenComplete` event with
/// `INIT_TRANSITION_DONE` 2. The `enable_interaction_after_initial_animation`
/// system adds a label component `InitTransitionDone` to any button component
/// which completed its appearance animation, to mark it as active.
/// 3. The `interaction` system only queries buttons with a `InitTransitionDone`
/// marker.
fn main() {
App::default()
.insert_resource(WindowDescriptor {
title: "Menu".to_string(),
width: 800.,
height: 400.,
present_mode: bevy::window::PresentMode::Fifo, // vsync
..default()
})
.add_plugins(DefaultPlugins)
.add_system(bevy::window::close_on_esc)
.add_system(interaction)
.add_system(enable_interaction_after_initial_animation)
.add_plugin(TweeningPlugin)
.add_plugin(WorldInspectorPlugin::new())
.add_startup_system(setup)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn_bundle(Camera2dBundle::default());
let font = asset_server.load("fonts/FiraMono-Regular.ttf");
commands
.spawn_bundle(NodeBundle {
style: Style {
position_type: PositionType::Absolute,
position: UiRect::all(Val::Px(0.)),
margin: UiRect::all(Val::Px(16.)),
padding: UiRect::all(Val::Px(16.)),
flex_direction: FlexDirection::ColumnReverse,
align_content: AlignContent::Center,
align_items: AlignItems::Center,
align_self: AlignSelf::Center,
justify_content: JustifyContent::Center,
..default()
},
color: UiColor(Color::NONE),
..default()
})
.insert(Name::new("menu"))
.with_children(|container| {
let mut start_time_ms = 0;
for (text, label) in [
("Continue", ButtonLabel::Continue),
("New Game", ButtonLabel::NewGame),
("Settings", ButtonLabel::Settings),
("Quit", ButtonLabel::Quit),
] {
let tween_scale = Tween::new(
EaseFunction::BounceOut,
Duration::from_secs(2),
TransformScaleLens {
start: Vec3::splat(0.01),
end: Vec3::ONE,
},
)
.with_completed_event(INIT_TRANSITION_DONE);
let animator = if start_time_ms > 0 {
let delay = Delay::new(Duration::from_millis(start_time_ms));
Animator::new(delay.then(tween_scale))
} else {
Animator::new(tween_scale)
};
start_time_ms += 500;
container
.spawn_bundle(ButtonBundle {
node: Node {
size: Vec2::new(300., 80.),
},
style: Style {
min_size: Size::new(Val::Px(300.), Val::Px(80.)),
margin: UiRect::all(Val::Px(8.)),
padding: UiRect::all(Val::Px(8.)),
align_content: AlignContent::Center,
align_items: AlignItems::Center,
align_self: AlignSelf::Center,
justify_content: JustifyContent::Center,
..default()
},
color: UiColor(NORMAL_COLOR),
transform: Transform::from_scale(Vec3::splat(0.01)),
..default()
})
.insert(Name::new(format!("button:{}", text)))
.insert(animator)
.insert(label)
.with_children(|parent| {
parent.spawn_bundle(TextBundle {
text: Text::from_section(
text.to_string(),
TextStyle {
font: font.clone(),
font_size: 48.0,
color: TEXT_COLOR,
},
)
.with_alignment(TextAlignment {
vertical: VerticalAlign::Center,
horizontal: HorizontalAlign::Center,
}),
..default()
});
});
}
});
}
fn enable_interaction_after_initial_animation(
mut commands: Commands,
mut reader: EventReader<TweenCompleted>,
) {
for event in reader.iter() {
if event.user_data == INIT_TRANSITION_DONE {
commands.entity(event.entity).insert(InitTransitionDone);
}
}
}
#[derive(Component)]
struct InitTransitionDone;
#[derive(Component, Clone, Copy)]
enum ButtonLabel {
Continue,
NewGame,
Settings,
Quit,
}
fn interaction(
mut interaction_query: Query<
(
&mut Animator<Transform>,
&Transform,
&Interaction,
&mut UiColor,
&ButtonLabel,
),
(Changed<Interaction>, With<InitTransitionDone>),
>,
) {
for (mut animator, transform, interaction, mut color, button_label) in &mut interaction_query {
match *interaction {
Interaction::Clicked => {
*color = CLICK_COLOR.into();
match button_label {
ButtonLabel::Continue => {
println!("Continue clicked");
}
ButtonLabel::NewGame => {
println!("NewGame clicked");
}
ButtonLabel::Settings => {
println!("Settings clicked");
}
ButtonLabel::Quit => {
println!("Quit clicked");
}
}
}
Interaction::Hovered => {
*color = HOVER_COLOR.into();
animator.set_tweenable(Tween::new(
EaseFunction::QuadraticIn,
Duration::from_millis(200),
TransformScaleLens {
start: Vec3::ONE,
end: Vec3::splat(1.1),
},
));
}
Interaction::None => {
*color = NORMAL_COLOR.into();
let start_scale = transform.scale;
animator.set_tweenable(Tween::new(
EaseFunction::QuadraticIn,
Duration::from_millis(200),
TransformScaleLens {
start: start_scale,
end: Vec3::ONE,
},
));
}
}
}
}
......@@ -171,6 +171,9 @@ pub mod lens;
mod plugin;
mod tweenable;
#[cfg(test)]
mod test_utils;
/// How many times to repeat a tween animation. See also: [`RepeatStrategy`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RepeatCount {
......@@ -475,12 +478,8 @@ mod tests {
#[cfg(feature = "bevy_asset")]
use bevy::reflect::TypeUuid;
use super::{lens::*, *};
/// Utility to compare floating-point values with a tolerance.
fn abs_diff_eq(a: f32, b: f32, tol: f32) -> bool {
(a - b).abs() < tol
}
use super::*;
use crate::test_utils::*;
struct DummyLens {
start: f32,
......@@ -511,7 +510,7 @@ mod tests {
let mut l = DummyLens { start: 0., end: 1. };
for r in [0_f32, 0.01, 0.3, 0.5, 0.9, 0.999, 1.] {
l.lerp(&mut c, r);
assert!(abs_diff_eq(c.value, r, 1e-5));
assert_approx_eq!(c.value, r);
}
}
......@@ -529,7 +528,7 @@ mod tests {
let mut l = DummyLens { start: 0., end: 1. };
for r in [0_f32, 0.01, 0.3, 0.5, 0.9, 0.999, 1.] {
l.lerp(&mut a, r);
assert!(abs_diff_eq(a.value, r, 1e-5));
assert_approx_eq!(a.value, r);
}
}
......@@ -628,32 +627,32 @@ mod tests {
);
let mut animator = Animator::new(tween);
assert_eq!(animator.state, AnimatorState::Playing);
assert!(animator.tweenable().progress().abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.);
animator.stop();
assert_eq!(animator.state, AnimatorState::Paused);
assert!(animator.tweenable().progress().abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.);
animator.tweenable_mut().set_progress(0.5);
assert_eq!(animator.state, AnimatorState::Paused);
assert!((animator.tweenable().progress() - 0.5).abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.5);
animator.tweenable_mut().rewind();
assert_eq!(animator.state, AnimatorState::Paused);
assert!(animator.tweenable().progress().abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.);
animator.tweenable_mut().set_progress(0.5);
animator.state = AnimatorState::Playing;
assert_eq!(animator.state, AnimatorState::Playing);
assert!((animator.tweenable().progress() - 0.5).abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.5);
animator.tweenable_mut().rewind();
assert_eq!(animator.state, AnimatorState::Playing);
assert!(animator.tweenable().progress().abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.);
animator.stop();
assert_eq!(animator.state, AnimatorState::Paused);
assert!(animator.tweenable().progress().abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.);
}
#[test]
......@@ -665,10 +664,10 @@ mod tests {
);
let mut animator = Animator::new(tween);
assert!(abs_diff_eq(animator.speed(), 1., 1e-5)); // default speed
assert_approx_eq!(animator.speed(), 1.); // default speed
animator.set_speed(2.4);
assert!(abs_diff_eq(animator.speed(), 2.4, 1e-5));
assert_approx_eq!(animator.speed(), 2.4);
let tween = Tween::<DummyComponent>::new(
EaseFunction::QuadraticInOut,
......@@ -677,7 +676,7 @@ mod tests {
);
let animator = Animator::new(tween).with_speed(3.5);
assert!(abs_diff_eq(animator.speed(), 3.5, 1e-5));
assert_approx_eq!(animator.speed(), 3.5);
}
#[test]
......@@ -746,32 +745,32 @@ mod tests {
);
let mut animator = AssetAnimator::new(Handle::<DummyAsset>::default(), tween);
assert_eq!(animator.state, AnimatorState::Playing);
assert!(animator.tweenable().progress().abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.);
animator.stop();
assert_eq!(animator.state, AnimatorState::Paused);
assert!(animator.tweenable().progress().abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.);
animator.tweenable_mut().set_progress(0.5);
assert_eq!(animator.state, AnimatorState::Paused);
assert!((animator.tweenable().progress() - 0.5).abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.5);
animator.tweenable_mut().rewind();
assert_eq!(animator.state, AnimatorState::Paused);
assert!(animator.tweenable().progress().abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.);
animator.tweenable_mut().set_progress(0.5);
animator.state = AnimatorState::Playing;
assert_eq!(animator.state, AnimatorState::Playing);
assert!((animator.tweenable().progress() - 0.5).abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.5);
animator.tweenable_mut().rewind();
assert_eq!(animator.state, AnimatorState::Playing);
assert!(animator.tweenable().progress().abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.);
animator.stop();
assert_eq!(animator.state, AnimatorState::Paused);
assert!(animator.tweenable().progress().abs() <= 1e-5);
assert_approx_eq!(animator.tweenable().progress(), 0.);
}
#[cfg(feature = "bevy_asset")]
......@@ -784,10 +783,10 @@ mod tests {
);
let mut animator = AssetAnimator::new(Handle::<DummyAsset>::default(), tween);
assert!(abs_diff_eq(animator.speed(), 1., 1e-5)); // default speed
assert_approx_eq!(animator.speed(), 1.); // default speed
animator.set_speed(2.4);
assert!(abs_diff_eq(animator.speed(), 2.4, 1e-5));
assert_approx_eq!(animator.speed(), 2.4);
let tween = Tween::new(
EaseFunction::QuadraticInOut,
......@@ -796,7 +795,7 @@ mod tests {
);
let animator = AssetAnimator::new(Handle::<DummyAsset>::default(), tween).with_speed(3.5);
assert!(abs_diff_eq(animator.speed(), 3.5, 1e-5));
assert_approx_eq!(animator.speed(), 3.5);
}
#[cfg(feature = "bevy_asset")]
......
/// Utility to compare floating-point values with a tolerance.
pub(crate) fn abs_diff_eq(a: f32, b: f32, tol: f32) -> bool {
(a - b).abs() < tol
}
/// Assert that two floating-point quantities are approximately equal.
///
/// This macro asserts that the absolute difference between the two first
/// arguments is strictly less than a tolerance factor, which can be explicitly
/// passed as third argument or implicitly defaults to `1e-5`.
///
/// # Usage
///
/// ```
/// let x = 3.500009;
/// assert_approx_eq!(x, 3.5); // default tolerance 1e-5
///
/// let x = 3.509;
/// assert_approx_eq!(x, 3.5, 0.01); // explicit tolerance
/// ```
macro_rules! assert_approx_eq {
($left:expr, $right:expr $(,)?) => {
match (&$left, &$right) {
(left_val, right_val) => {
assert!(
abs_diff_eq(*left_val, *right_val, 1e-5),
"assertion failed: expected={} actual={} delta={} tol=1e-5(default)",
left_val,
right_val,
(left_val - right_val).abs(),
);
}
}
};
($left:expr, $right:expr, $tol:expr $(,)?) => {
match (&$left, &$right, &$tol) {
(left_val, right_val, tol_val) => {
assert!(
abs_diff_eq(*left_val, *right_val, *tol_val),
"assertion failed: expected={} actual={} delta={} tol={}",
left_val,
right_val,
(left_val - right_val).abs(),
tol_val
);
}
}
};
}
pub(crate) use assert_approx_eq;
......@@ -301,7 +301,7 @@ impl<T: 'static> Tween<T> {
/// # use std::time::Duration;
/// let tween1 = Tween::new(
/// EaseFunction::QuadraticInOut,
/// Duration::from_secs_f32(1.0),
/// Duration::from_secs(1),
/// TransformPositionLens {
/// start: Vec3::ZERO,
/// end: Vec3::new(3.5, 0., 0.),
......@@ -309,7 +309,7 @@ impl<T: 'static> Tween<T> {
/// );
/// let tween2 = Tween::new(
/// EaseFunction::QuadraticInOut,
/// Duration::from_secs_f32(1.0),
/// Duration::from_secs(1),
/// TransformRotationLens {
/// start: Quat::IDENTITY,
/// end: Quat::from_rotation_x(90.0_f32.to_radians()),
......@@ -333,7 +333,7 @@ impl<T> Tween<T> {
/// # use std::time::Duration;
/// let tween = Tween::new(
/// EaseFunction::QuadraticInOut,
/// Duration::from_secs_f32(1.0),
/// Duration::from_secs(1),
/// TransformPositionLens {
/// start: Vec3::ZERO,
/// end: Vec3::new(3.5, 0., 0.),
......@@ -369,7 +369,7 @@ impl<T> Tween<T> {
/// let tween = Tween::new(
/// // [...]
/// # EaseFunction::QuadraticInOut,
/// # Duration::from_secs_f32(1.0),
/// # Duration::from_secs(1),
/// # TransformPositionLens {
/// # start: Vec3::ZERO,
/// # end: Vec3::new(3.5, 0., 0.),
......@@ -887,14 +887,8 @@ mod tests {
use bevy::ecs::{event::Events, system::SystemState};
use crate::lens::*;
use super::*;
/// Utility to compare floating-point values with a tolerance.
fn abs_diff_eq(a: f32, b: f32, tol: f32) -> bool {
(a - b).abs() < tol
}
use crate::{lens::*, test_utils::*};
#[derive(Default, Copy, Clone)]
struct CallbackMonitor {
......@@ -1005,7 +999,7 @@ mod tests {
assert_eq!(tween.event_data.unwrap(), USER_DATA);
// Loop over 2.2 seconds, so greater than one ping-pong loop
let tick_duration = Duration::from_secs_f32(0.2);
let tick_duration = Duration::from_millis(200);
for i in 1..=11 {
// Calculate expected values
let (progress, times_completed, mut direction, expected_state, just_completed) =
......@@ -1136,7 +1130,7 @@ mod tests {
// Check actual values
assert_eq!(tween.direction(), direction);
assert_eq!(actual_state, expected_state);
assert!(abs_diff_eq(tween.progress(), progress, 1e-5));
assert_approx_eq!(tween.progress(), progress);
assert_eq!(tween.times_completed(), times_completed);
assert!(transform
.translation
......@@ -1163,7 +1157,7 @@ mod tests {
// Rewind
tween.rewind();
assert_eq!(tween.direction(), *tweening_direction); // does not change
assert!(abs_diff_eq(tween.progress(), 0., 1e-5));
assert_approx_eq!(tween.progress(), 0.);
assert_eq!(tween.times_completed(), 0);
// Dummy tick to update target
......@@ -1200,26 +1194,26 @@ mod tests {
// Default
assert_eq!(tween.direction(), TweeningDirection::Forward);
assert!(abs_diff_eq(tween.progress(), 0.0, 1e-5));
assert_approx_eq!(tween.progress(), 0.0);
// no-op
tween.set_direction(TweeningDirection::Forward);
assert_eq!(tween.direction(), TweeningDirection::Forward);
assert!(abs_diff_eq(tween.progress(), 0.0, 1e-5));
assert_approx_eq!(tween.progress(), 0.0);
// Backward
tween.set_direction(TweeningDirection::Backward);
assert_eq!(tween.direction(), TweeningDirection::Backward);
// progress is independent of direction
assert!(abs_diff_eq(tween.progress(), 0.0, 1e-5));
assert_approx_eq!(tween.progress(), 0.0);
// Progress-invariant
tween.set_direction(TweeningDirection::Forward);
tween.set_progress(0.3);
assert!(abs_diff_eq(tween.progress(), 0.3, 1e-5));
assert_approx_eq!(tween.progress(), 0.3);
tween.set_direction(TweeningDirection::Backward);
// progress is independent of direction
assert!(abs_diff_eq(tween.progress(), 0.3, 1e-5));
assert_approx_eq!(tween.progress(), 0.3);
let (mut world, entity, mut transform) = make_test_env();
let mut event_writer_system_state: SystemState<EventWriter<TweenCompleted>> =
......@@ -1228,14 +1222,14 @@ mod tests {
// Progress always increases alongside the current direction
tween.set_direction(TweeningDirection::Backward);
assert!(abs_diff_eq(tween.progress(), 0.3, 1e-5));
assert_approx_eq!(tween.progress(), 0.3);
tween.tick(
Duration::from_secs_f32(0.1),
Duration::from_millis(100),
&mut transform,
entity,
&mut event_writer,
);
assert!(abs_diff_eq(tween.progress(), 0.4, 1e-5));
assert_approx_eq!(tween.progress(), 0.4);
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.6), 1e-5));
}
......@@ -1244,7 +1238,7 @@ mod tests {
fn seq_tick() {
let tween1 = Tween::new(
EaseMethod::Linear,
Duration::from_secs_f32(1.0),
Duration::from_secs(1),
TransformPositionLens {
start: Vec3::ZERO,
end: Vec3::ONE,
......@@ -1252,7 +1246,7 @@ mod tests {
);
let tween2 = Tween::new(
EaseMethod::Linear,
Duration::from_secs_f32(1.0),
Duration::from_secs(1),
TransformRotationLens {
start: Quat::IDENTITY,
end: Quat::from_rotation_x(90_f32.to_radians()),
......@@ -1267,7 +1261,7 @@ mod tests {
for i in 1..=16 {
let state = seq.tick(
Duration::from_secs_f32(0.2),
Duration::from_millis(200),
&mut transform,
entity,
&mut event_writer,
......@@ -1317,9 +1311,9 @@ mod tests {
// - Finish the first tween
// - Start and finish the second tween
// - Start the third tween
for delta in [0.5, 2.0] {
for delta_ms in [500, 2000] {
seq.tick(
Duration::from_secs_f32(delta),
Duration::from_millis(delta_ms),
&mut transform,
entity,
&mut event_writer,
......@@ -1335,7 +1329,7 @@ mod tests {
let mut seq = Sequence::new((1..5).map(|i| {
Tween::new(
EaseMethod::Linear,
Duration::from_secs_f32(0.2 * i as f32),
Duration::from_millis(200 * i),
TransformPositionLens {
start: Vec3::ZERO,
end: Vec3::ONE,
......@@ -1346,9 +1340,9 @@ mod tests {
let mut progress = 0.;
for i in 1..5 {
assert_eq!(seq.index(), i - 1);
assert!((seq.progress() - progress).abs() < 1e-5);
let secs = 0.2 * i as f32;
assert_eq!(seq.current().duration(), Duration::from_secs_f32(secs));
assert_approx_eq!(seq.progress(), progress);
let duration = Duration::from_millis(200 * i as u64);
assert_eq!(seq.current().duration(), duration);
progress += 0.25;
seq.set_progress(progress);
assert_eq!(seq.times_completed(), if i == 4 { 1 } else { 0 });
......@@ -1395,7 +1389,7 @@ mod tests {
},
);
let mut tracks = Tracks::new([tween1, tween2]);
assert_eq!(tracks.duration(), Duration::from_secs_f32(1.)); // max(1., 0.8)
assert_eq!(tracks.duration(), Duration::from_secs(1)); // max(1., 0.8)
let (mut world, entity, mut transform) = make_test_env();
let mut system_state: SystemState<EventWriter<TweenCompleted>> =
......@@ -1404,7 +1398,7 @@ mod tests {
for i in 1..=6 {
let state = tracks.tick(
Duration::from_secs_f32(0.2),
Duration::from_millis(200),
&mut transform,
entity,
&mut event_writer,
......@@ -1413,7 +1407,7 @@ mod tests {
assert_eq!(state, TweenState::Active);
assert_eq!(tracks.times_completed(), 0);
let r = i as f32 * 0.2;
assert!((tracks.progress() - r).abs() < 1e-5);
assert_approx_eq!(tracks.progress(), r);
let alpha_deg = 22.5 * i as f32;
assert!(transform.translation.abs_diff_eq(Vec3::splat(r), 1e-5));
assert!(transform
......@@ -1422,7 +1416,7 @@ mod tests {
} else {
assert_eq!(state, TweenState::Completed);
assert_eq!(tracks.times_completed(), 1);
assert!((tracks.progress() - 1.).abs() < 1e-5);
assert_approx_eq!(tracks.progress(), 1.);
assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
assert!(transform
.rotation
......@@ -1432,13 +1426,13 @@ mod tests {
tracks.rewind();
assert_eq!(tracks.times_completed(), 0);
assert!(tracks.progress().abs() < 1e-5);
assert_approx_eq!(tracks.progress(), 0.);
tracks.set_progress(0.9);
assert!((tracks.progress() - 0.9).abs() < 1e-5);
assert_approx_eq!(tracks.progress(), 0.9);
// tick to udpate state (set_progress() does not update state)
let state = tracks.tick(
Duration::from_secs_f32(0.),
Duration::ZERO,
&mut transform,
Entity::from_raw(0),
&mut event_writer,
......@@ -1447,10 +1441,10 @@ mod tests {
assert_eq!(tracks.times_completed(), 0);
tracks.set_progress(3.2);
assert!((tracks.progress() - 1.).abs() < 1e-5);
assert_approx_eq!(tracks.progress(), 1.);
// tick to udpate state (set_progress() does not update state)
let state = tracks.tick(
Duration::from_secs_f32(0.),
Duration::ZERO,
&mut transform,
Entity::from_raw(0),
&mut event_writer,
......@@ -1459,10 +1453,10 @@ mod tests {
assert_eq!(tracks.times_completed(), 1); // no looping
tracks.set_progress(-0.5);
assert!(tracks.progress().abs() < 1e-5);
assert_approx_eq!(tracks.progress(), 0.);
// tick to udpate state (set_progress() does not update state)
let state = tracks.tick(
Duration::from_secs_f32(0.),
Duration::ZERO,
&mut transform,
Entity::from_raw(0),
&mut event_writer,
......@@ -1491,7 +1485,7 @@ mod tests {
{
let tweenable: &dyn Tweenable<Transform> = &delay;
assert_eq!(tweenable.duration(), duration);
assert!(tweenable.progress().abs() < 1e-5);
assert_approx_eq!(tweenable.progress(), 0.);
}
// Dummy world and event writer
......@@ -1513,11 +1507,11 @@ mod tests {
assert_eq!(state, TweenState::Active);
assert_eq!(tweenable.times_completed(), 0);
let r = i as f32 * 0.2;
assert!((tweenable.progress() - r).abs() < 1e-5);
assert_approx_eq!(tweenable.progress(), r);
} else {
assert_eq!(state, TweenState::Completed);
assert_eq!(tweenable.times_completed(), 1);
assert!((tweenable.progress() - 1.).abs() < 1e-5);
assert_approx_eq!(tweenable.progress(), 1.);
}
}
}
......@@ -1526,16 +1520,16 @@ mod tests {
tweenable.rewind();
assert_eq!(tweenable.times_completed(), 0);
assert!(abs_diff_eq(tweenable.progress(), 0., 1e-5));
assert_approx_eq!(tweenable.progress(), 0.);
let state = tweenable.tick(Duration::ZERO, &mut transform, entity, &mut event_writer);
assert_eq!(state, TweenState::Active);
tweenable.set_progress(0.3);
assert_eq!(tweenable.times_completed(), 0);
assert!(abs_diff_eq(tweenable.progress(), 0.3, 1e-5));
assert_approx_eq!(tweenable.progress(), 0.3);
tweenable.set_progress(1.);
assert_eq!(tweenable.times_completed(), 1);
assert!(abs_diff_eq(tweenable.progress(), 1., 1e-5));
assert_approx_eq!(tweenable.progress(), 1.);
}
#[test]
......@@ -1550,7 +1544,7 @@ mod tests {
.with_repeat_count(RepeatCount::Finite(5))
.with_repeat_strategy(RepeatStrategy::Repeat);
assert!(abs_diff_eq(tween.progress(), 0., 1e-5));
assert_approx_eq!(tween.progress(), 0.);
let (mut world, entity, mut transform) = make_test_env();
let mut event_writer_system_state: SystemState<EventWriter<TweenCompleted>> =
......@@ -1566,7 +1560,7 @@ mod tests {
);
assert_eq!(TweenState::Active, state);
assert_eq!(0, tween.times_completed());
assert!(abs_diff_eq(tween.progress(), 0.1, 1e-5));
assert_approx_eq!(tween.progress(), 0.1);
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5));
// 130%
......@@ -1578,7 +1572,7 @@ mod tests {
);
assert_eq!(TweenState::Active, state);
assert_eq!(1, tween.times_completed());
assert!(abs_diff_eq(tween.progress(), 0.3, 1e-5));
assert_approx_eq!(tween.progress(), 0.3);
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.3), 1e-5));
// 480%
......@@ -1590,7 +1584,7 @@ mod tests {
);
assert_eq!(TweenState::Active, state);
assert_eq!(4, tween.times_completed());
assert!(abs_diff_eq(tween.progress(), 0.8, 1e-5));
assert_approx_eq!(tween.progress(), 0.8);
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.8), 1e-5));
// 500% - done
......@@ -1602,7 +1596,7 @@ mod tests {
);
assert_eq!(TweenState::Completed, state);
assert_eq!(5, tween.times_completed());
assert!(abs_diff_eq(tween.progress(), 1.0, 1e-5));
assert_approx_eq!(tween.progress(), 1.0);
assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
}
......@@ -1612,7 +1606,7 @@ mod tests {
.with_repeat_count(RepeatCount::Finite(4))
.with_repeat_strategy(RepeatStrategy::MirroredRepeat);
assert!(abs_diff_eq(tween.progress(), 0., 1e-5));
assert_approx_eq!(tween.progress(), 0.);
let (mut world, entity, mut transform) = make_test_env();
let mut event_writer_system_state: SystemState<EventWriter<TweenCompleted>> =
......@@ -1629,14 +1623,14 @@ mod tests {
assert_eq!(TweenState::Active, state);
assert_eq!(TweeningDirection::Forward, tween.direction());
assert_eq!(0, tween.times_completed());
assert!(abs_diff_eq(tween.progress(), 0.1, 1e-5));
assert_approx_eq!(tween.progress(), 0.1);
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5));
// rewind
tween.rewind();
assert_eq!(TweeningDirection::Forward, tween.direction());
assert_eq!(0, tween.times_completed());
assert!(abs_diff_eq(tween.progress(), 0., 1e-5));
assert_approx_eq!(tween.progress(), 0.);
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5)); // no-op, rewind doesn't apply Lens
// 120% - mirror
......@@ -1649,14 +1643,14 @@ mod tests {
assert_eq!(TweeningDirection::Backward, tween.direction());
assert_eq!(TweenState::Active, state);
assert_eq!(1, tween.times_completed());
assert!(abs_diff_eq(tween.progress(), 0.2, 1e-5));
assert_approx_eq!(tween.progress(), 0.2);
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.8), 1e-5));
// rewind
tween.rewind();
assert_eq!(TweeningDirection::Forward, tween.direction()); // restored
assert_eq!(0, tween.times_completed());
assert!(abs_diff_eq(tween.progress(), 0., 1e-5));
assert_approx_eq!(tween.progress(), 0.);
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.8), 1e-5)); // no-op, rewind doesn't apply Lens
// 400% - done mirror (because Completed freezes the state)
......@@ -1669,14 +1663,14 @@ mod tests {
assert_eq!(TweenState::Completed, state);
assert_eq!(TweeningDirection::Backward, tween.direction()); // frozen from last loop
assert_eq!(4, tween.times_completed());
assert!(abs_diff_eq(tween.progress(), 1., 1e-5)); // Completed
assert_approx_eq!(tween.progress(), 1.); // Completed
assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
// rewind
tween.rewind();
assert_eq!(TweeningDirection::Forward, tween.direction()); // restored
assert_eq!(0, tween.times_completed());
assert!(abs_diff_eq(tween.progress(), 0., 1e-5));
assert_approx_eq!(tween.progress(), 0.);
assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5)); // no-op, rewind doesn't apply Lens
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment