diff --git a/src/lib.rs b/src/lib.rs index 551b92e..f745066 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,27 +151,25 @@ //! [`Sprite`]: https://docs.rs/bevy/0.8.0/bevy/sprite/struct.Sprite.html //! [`Transform`]: https://docs.rs/bevy/0.8.0/bevy/transform/components/struct.Transform.html -use bevy::prelude::*; use std::time::Duration; #[cfg(feature = "bevy_asset")] use bevy::asset::Asset; - +use bevy::prelude::*; use interpolation::Ease as IEase; pub use interpolation::{EaseFunction, Lerp}; -pub mod lens; -mod plugin; -mod tweenable; - pub use lens::Lens; +#[cfg(feature = "bevy_asset")] +pub use plugin::asset_animator_system; pub use plugin::{component_animator_system, AnimationSystem, TweeningPlugin}; pub use tweenable::{ BoxedTweenable, Delay, Sequence, Tracks, Tween, TweenCompleted, TweenState, Tweenable, }; -#[cfg(feature = "bevy_asset")] -pub use plugin::asset_animator_system; +pub mod lens; +mod plugin; +mod tweenable; /// How many times to repeat a tween animation. See also: [`RepeatStrategy`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -388,35 +386,6 @@ macro_rules! animator_impl { } } - /// Set the current animation playback progress. - /// - /// See [`progress()`] for details on the meaning. - /// - /// [`progress()`]: Animator::progress - pub fn set_progress(&mut self, progress: f32) { - if let Some(tweenable) = &mut self.tweenable { - tweenable.set_progress(progress) - } - } - - /// Get the current progress of the tweenable. See [`Tweenable::progress`] for - /// details. - /// - /// For sequences, the progress is measured over the entire sequence, from 0 at - /// the start of the first child tweenable to 1 at the end of the last one. - /// - /// For tracks (parallel execution), the progress is measured like a sequence - /// over the longest "path" of child tweenables. In other words, this is the - /// current elapsed time over the total tweenable duration. - #[must_use] - pub fn progress(&self) -> f32 { - if let Some(tweenable) = &self.tweenable { - tweenable.progress() - } else { - 0. - } - } - /// Ticks the tween, if present. See [`Tweenable::tick`] for details. pub fn tick( &mut self, @@ -549,6 +518,8 @@ mod tests { #[cfg(feature = "bevy_asset")] use bevy::reflect::TypeUuid; + use crate::tweenable::TweenableExt; + use super::{lens::*, *}; struct DummyLens { @@ -690,32 +661,32 @@ mod tests { ); let mut animator = Animator::new(tween); assert_eq!(animator.state, AnimatorState::Playing); - assert!(animator.progress().abs() <= 1e-5); + assert!(animator.tweenable().unwrap().progress().abs() <= 1e-5); animator.stop(); assert_eq!(animator.state, AnimatorState::Paused); - assert!(animator.progress().abs() <= 1e-5); + assert!(animator.tweenable().unwrap().progress().abs() <= 1e-5); - animator.set_progress(0.5); + animator.tweenable_mut().unwrap().set_progress(0.5); assert_eq!(animator.state, AnimatorState::Paused); - assert!((animator.progress() - 0.5).abs() <= 1e-5); + assert!((animator.tweenable().unwrap().progress() - 0.5).abs() <= 1e-5); animator.rewind(); assert_eq!(animator.state, AnimatorState::Paused); - assert!(animator.progress().abs() <= 1e-5); + assert!(animator.tweenable().unwrap().progress().abs() <= 1e-5); - animator.set_progress(0.5); + animator.tweenable_mut().unwrap().set_progress(0.5); animator.state = AnimatorState::Playing; assert_eq!(animator.state, AnimatorState::Playing); - assert!((animator.progress() - 0.5).abs() <= 1e-5); + assert!((animator.tweenable().unwrap().progress() - 0.5).abs() <= 1e-5); animator.rewind(); assert_eq!(animator.state, AnimatorState::Playing); - assert!(animator.progress().abs() <= 1e-5); + assert!(animator.tweenable().unwrap().progress().abs() <= 1e-5); animator.stop(); assert_eq!(animator.state, AnimatorState::Paused); - assert!(animator.progress().abs() <= 1e-5); + assert!(animator.tweenable().unwrap().progress().abs() <= 1e-5); } /// AssetAnimator::new() @@ -780,31 +751,31 @@ mod tests { ); let mut animator = AssetAnimator::new(Handle::::default(), tween); assert_eq!(animator.state, AnimatorState::Playing); - assert!(animator.progress().abs() <= 1e-5); + assert!(animator.tweenable().unwrap().progress().abs() <= 1e-5); animator.stop(); assert_eq!(animator.state, AnimatorState::Paused); - assert!(animator.progress().abs() <= 1e-5); + assert!(animator.tweenable().unwrap().progress().abs() <= 1e-5); - animator.set_progress(0.5); + animator.tweenable_mut().unwrap().set_progress(0.5); assert_eq!(animator.state, AnimatorState::Paused); - assert!((animator.progress() - 0.5).abs() <= 1e-5); + assert!((animator.tweenable().unwrap().progress() - 0.5).abs() <= 1e-5); animator.rewind(); assert_eq!(animator.state, AnimatorState::Paused); - assert!(animator.progress().abs() <= 1e-5); + assert!(animator.tweenable().unwrap().progress().abs() <= 1e-5); - animator.set_progress(0.5); + animator.tweenable_mut().unwrap().set_progress(0.5); animator.state = AnimatorState::Playing; assert_eq!(animator.state, AnimatorState::Playing); - assert!((animator.progress() - 0.5).abs() <= 1e-5); + assert!((animator.tweenable().unwrap().progress() - 0.5).abs() <= 1e-5); animator.rewind(); assert_eq!(animator.state, AnimatorState::Playing); - assert!(animator.progress().abs() <= 1e-5); + assert!(animator.tweenable().unwrap().progress().abs() <= 1e-5); animator.stop(); assert_eq!(animator.state, AnimatorState::Paused); - assert!(animator.progress().abs() <= 1e-5); + assert!(animator.tweenable().unwrap().progress().abs() <= 1e-5); } } diff --git a/src/tweenable.rs b/src/tweenable.rs index c140df7..054c4f6 100644 --- a/src/tweenable.rs +++ b/src/tweenable.rs @@ -28,10 +28,9 @@ use crate::{EaseMethod, Lens, RepeatCount, RepeatStrategy, TweeningDirection}; /// # struct MyTweenable; /// # impl Tweenable for MyTweenable { /// # fn duration(&self) -> Duration { unimplemented!() } -/// # fn set_progress(&mut self, progress: f32) { unimplemented!() } -/// # fn progress(&self) -> f32 { unimplemented!() } +/// # fn elapsed(&self) -> f32 { unimplemented!() } +/// # fn set_elapsed(&mut self, progress: f32) { unimplemented!() } /// # fn tick(&mut self, delta: Duration, target: &mut Transform, entity: Entity, event_writer: &mut EventWriter) -> TweenState { unimplemented!() } -/// # fn times_completed(&self) -> u32 { unimplemented!() } /// # fn rewind(&mut self) { unimplemented!() } /// # } /// @@ -98,45 +97,45 @@ pub struct TweenCompleted { #[derive(Debug)] struct AnimClock { - elapsed: Duration, + /// The duration of one iteration of the animation. duration: Duration, - times_completed: u32, + /// The total time elapsed playing this animation, within the range \[0, + /// total_duration\]. + elapsed: Duration, total_duration: TotalDuration, strategy: RepeatStrategy, + direction: TweeningDirection, } impl AnimClock { fn new(duration: Duration) -> Self { Self { - elapsed: Duration::ZERO, duration, + elapsed: Duration::ZERO, total_duration: compute_total_duration(duration, RepeatCount::default()), - times_completed: 0, strategy: RepeatStrategy::default(), + direction: TweeningDirection::default(), } } - fn record_completions(&mut self, times_completed: u32) { - self.times_completed = self.times_completed.saturating_add(times_completed); - } - fn tick(&mut self, tick: Duration) -> u32 { let duration = self.duration.as_nanos(); - let before = self.elapsed.as_nanos() / duration; - self.elapsed = self.elapsed.saturating_add(tick); - if let TotalDuration::Finite(duration) = self.total_duration { - self.elapsed = self.elapsed.min(duration); - } - (self.elapsed.as_nanos() / duration - before) as u32 - } + let times_completed_before = self.elapsed.as_nanos() / duration; + self.set_elapsed(self.elapsed.saturating_add(tick)); - fn set_progress(&mut self, progress: f32) { - self.elapsed = self.duration.mul_f32(progress.max(0.)); + let times_completed = (self.elapsed.as_nanos() / duration - times_completed_before) as u32; + if self.strategy == RepeatStrategy::MirroredRepeat && times_completed & 1 != 0 { + self.direction = !self.direction; + } + times_completed } - fn progress(&self) -> f32 { - self.elapsed.as_secs_f32() / self.duration.as_secs_f32() + fn set_elapsed(&mut self, time: Duration) { + self.elapsed = time; + if let TotalDuration::Finite(duration) = self.total_duration { + self.elapsed = self.elapsed.min(duration); + } } fn state(&self) -> TweenState { @@ -152,8 +151,15 @@ impl AnimClock { } } + fn progress(&self) -> Duration { + if let TotalDuration::Finite(duration) = self.total_duration && self.elapsed == duration { + self.duration + } else { + Duration::from_nanos((self.elapsed.as_nanos() % self.duration.as_nanos()) as u64) + } + } + fn reset(&mut self) { - self.times_completed = 0; self.elapsed = Duration::ZERO; } } @@ -174,47 +180,43 @@ fn compute_total_duration(duration: Duration, count: RepeatCount) -> TotalDurati /// An animatable entity, either a single [`Tween`] or a collection of them. pub trait Tweenable: Send + Sync { - /// Get the total duration of the animation. + /// Get the duration of one iteration of the animation. /// - /// This is always the duration of a single iteration, even when looping. + /// This is always the duration of a single iteration, even when repeating. /// /// Note that for [`RepeatStrategy::MirroredRepeat`], this is the duration /// of a single way, either from start to end or back from end to start. - /// The total "loop" duration start -> end -> start to reach back the - /// same state in this case is the double of the returned value. + /// The total "loop" duration start -> end -> start is twice the returned + /// value. fn duration(&self) -> Duration; - /// Set the current animation playback progress. - /// - /// See [`progress()`] for details on the meaning. + /// Get the current animation elapsed playback time. /// - /// [`progress()`]: Tweenable::progress - fn set_progress(&mut self, progress: f32); + /// Values greater than [`duration`][Self::duration] mean the animation has + /// repeated. + fn elapsed(&self) -> Duration; - /// Get the current progress in \[0:1\] of the animation. + /// Set the current animation elapsed playback time. /// - /// While looping, the exact value `1.0` is never reached, since the - /// tweenable loops over to `0.0` immediately when it changes direction at - /// either endpoint. Upon completion, the tweenable always reports exactly - /// `1.0`. - fn progress(&self) -> f32; + /// See [`elapsed`][Self::elapsed] for details on the meaning. + fn set_elapsed(&mut self, time: Duration); /// Tick the animation, advancing it by the given delta time and mutating /// the given target component or asset. /// /// This returns [`TweenState::Active`] if the tweenable didn't reach its - /// final state yet (progress < `1.0`), or [`TweenState::Completed`] if - /// the tweenable completed this tick. Only non-looping tweenables return - /// a completed state, since looping ones continue forever. + /// final state yet (elapsed < total_duration), or [`TweenState::Completed`] + /// if the tweenable completed this tick. Only non-looping tweenables + /// return a completed state, since looping ones continue forever. /// /// Calling this method with a duration of [`Duration::ZERO`] is valid, and /// updates the target to the current state of the tweenable without /// actually modifying the tweenable state. This is useful after certain - /// operations like [`rewind()`] or [`set_progress()`] whose effect is + /// operations like [`rewind()`] or [`set_elapsed()`] whose effect is /// otherwise only visible on target on next frame. /// /// [`rewind()`]: Tweenable::rewind - /// [`set_progress()`]: Tweenable::set_progress + /// [`set_elapsed()`]: Tweenable::set_elapsed fn tick( &mut self, delta: Duration, @@ -223,15 +225,6 @@ pub trait Tweenable: Send + Sync { event_writer: &mut EventWriter, ) -> TweenState; - /// Get the number of times this tweenable completed. - /// - /// For looping animations, this returns the number of times a single - /// playback was completed. In the case of - /// [`RepeatStrategy::MirroredRepeat`] this corresponds to a playback in - /// a single direction, so tweening from start to end and back to start - /// counts as two completed times (one forward, one backward). - fn times_completed(&self) -> u32; - /// Rewind the animation to its starting state. /// /// Note that the starting state depends on the current direction. For @@ -240,6 +233,47 @@ pub trait Tweenable: Send + Sync { fn rewind(&mut self); } +/// Utilities on [`Tweenable`]s. +pub trait TweenableExt { + /// Set the current animation playback progress. + /// + /// See [`progress`][Self::progress] for details on the meaning. + fn set_progress(&mut self, progress: f32); + + /// Get the current progress of the animation. + /// + /// The integer part represents the number of repetitions while the + /// fractional part represents the progress within one iteration of the + /// animation. + fn progress(&self) -> f32; + + /// Get the number of times this tweenable completed. + /// + /// For looping animations, this returns the number of times a single + /// playback was completed. In the case of + /// [`RepeatStrategy::MirroredRepeat`] this corresponds to a playback in a + /// single direction, so tweening from start -> end -> start counts as two + /// completions (one forward, one backward). + fn times_completed(&self) -> u32; +} + +impl> TweenableExt for U { + fn set_progress(&mut self, progress: f32) { + let progress = progress.max(0.); + // TODO this should use try_from_secs_f64 and saturate once stable + // Duration::try_from_secs_f32(self.duration().as_secs_f32() * progress) + self.set_elapsed(self.duration().mul_f32(progress)); + } + + fn progress(&self) -> f32 { + self.elapsed().as_secs_f32() / self.duration().as_secs_f32() + } + + fn times_completed(&self) -> u32 { + self.progress() as u32 + } +} + impl From for BoxedTweenable { fn from(d: Delay) -> Self { Box::new(d) @@ -273,7 +307,6 @@ pub type CompletedCallback = dyn Fn(Entity, &Tween) + Send + Sync + 'stati pub struct Tween { ease_function: EaseMethod, clock: AnimClock, - direction: TweeningDirection, lens: Box + Send + Sync + 'static>, on_completed: Option>>, event_data: Option, @@ -337,7 +370,6 @@ impl Tween { Self { ease_function: ease_function.into(), clock: AnimClock::new(duration), - direction: TweeningDirection::Forward, lens: Box::new(lens), on_completed: None, event_data: None, @@ -395,7 +427,7 @@ impl Tween { /// potentially changes. To force a target state change, call /// [`Tweenable::tick()`] with a zero delta (`Duration::ZERO`). pub fn set_direction(&mut self, direction: TweeningDirection) { - self.direction = direction; + self.clock.direction = direction; } /// Set the playback direction of the tween. @@ -403,7 +435,7 @@ impl Tween { /// See [`Tween::set_direction()`]. #[must_use] pub fn with_direction(mut self, direction: TweeningDirection) -> Self { - self.direction = direction; + self.clock.direction = direction; self } @@ -412,10 +444,15 @@ impl Tween { /// See [`TweeningDirection`] for details. #[must_use] pub fn direction(&self) -> TweeningDirection { - self.direction + self.clock.direction } /// Set the number of times to repeat the animation. + /// + /// [`Tweenable::elapsed`] will be within the range \[0, $end\] where $end + /// can be any value <= to the [`RepeatCount`]. Upon completion, + /// [`Tweenable::elapsed`] will equal the total animation duration specified + /// in the [`RepeatCount`]. #[must_use] pub fn with_repeat_count(mut self, count: RepeatCount) -> Self { self.clock.total_duration = compute_total_duration(self.clock.duration, count); @@ -473,12 +510,12 @@ impl Tweenable for Tween { self.clock.duration } - fn set_progress(&mut self, progress: f32) { - self.clock.set_progress(progress); + fn elapsed(&self) -> Duration { + self.clock.elapsed } - fn progress(&self) -> f32 { - self.clock.progress() + fn set_elapsed(&mut self, time: Duration) { + self.clock.set_elapsed(time); } fn tick( @@ -494,16 +531,11 @@ impl Tweenable for Tween { // Tick the animation clock let times_completed = self.clock.tick(delta); - self.clock.record_completions(times_completed); - if self.clock.strategy == RepeatStrategy::MirroredRepeat && times_completed & 1 != 0 { - self.direction = !self.direction; - } - let progress = self.progress(); // Apply the lens, even if the animation finished, to ensure the state is // consistent - let mut factor = progress; - if self.direction.is_backward() { + let mut factor = self.clock.progress().as_secs_f32() / self.clock.duration.as_secs_f32(); + if self.clock.direction.is_backward() { factor = 1. - factor; } let factor = self.ease_function.sample(factor); @@ -525,10 +557,6 @@ impl Tweenable for Tween { self.clock.state() } - fn times_completed(&self) -> u32 { - self.clock.times_completed - } - fn rewind(&mut self) { self.clock.reset(); } @@ -538,9 +566,7 @@ impl Tweenable for Tween { pub struct Sequence { tweens: Vec>, index: usize, - duration: Duration, - time: Duration, - times_completed: u32, + clock: AnimClock, } impl Sequence { @@ -550,7 +576,6 @@ impl Sequence { #[must_use] pub fn new(items: impl IntoIterator>>) -> Self { let tweens: Vec<_> = items.into_iter().map(Into::into).collect(); - assert!(!tweens.is_empty()); let duration = tweens .iter() .map(AsRef::as_ref) @@ -559,9 +584,7 @@ impl Sequence { Self { tweens, index: 0, - duration, - time: Duration::ZERO, - times_completed: 0, + clock: AnimClock::new(duration), } } @@ -573,9 +596,7 @@ impl Sequence { Self { tweens: vec![boxed], index: 0, - duration, - time: Duration::ZERO, - times_completed: 0, + clock: AnimClock::new(duration), } } @@ -585,16 +606,16 @@ impl Sequence { Self { tweens: Vec::with_capacity(capacity), index: 0, - duration: Duration::ZERO, - time: Duration::ZERO, - times_completed: 0, + clock: AnimClock::new(Duration::ZERO), } } /// Append a [`Tweenable`] to this sequence. #[must_use] pub fn then(mut self, tween: impl Tweenable + Send + Sync + 'static) -> Self { - self.duration += tween.duration(); + self.clock.duration += tween.duration(); + self.clock.total_duration = + compute_total_duration(self.clock.duration, RepeatCount::default()); self.tweens.push(Box::new(tween)); self } @@ -614,38 +635,26 @@ impl Sequence { impl Tweenable for Sequence { fn duration(&self) -> Duration { - self.duration + self.clock.duration } - fn set_progress(&mut self, progress: f32) { - self.times_completed = if progress >= 1. { 1 } else { 0 }; - let progress = progress.clamp(0., 1.); // not looping - // Set the total sequence progress - let total_elapsed_secs = self.duration().as_secs_f64() * progress as f64; - self.time = Duration::from_secs_f64(total_elapsed_secs); - - // Find which tween is active in the sequence - let mut accum_duration = 0.; - for index in 0..self.tweens.len() { - let tween = &mut self.tweens[index]; - let tween_duration = tween.duration().as_secs_f64(); - if total_elapsed_secs < accum_duration + tween_duration { - self.index = index; - let local_duration = total_elapsed_secs - accum_duration; - tween.set_progress((local_duration / tween_duration) as f32); - // TODO?? set progress of other tweens after that one to 0. ?? - return; - } - tween.set_progress(1.); // ?? to prepare for next loop/rewind? - accum_duration += tween_duration; - } - - // None found; sequence ended - self.index = self.tweens.len(); + fn elapsed(&self) -> Duration { + self.clock.elapsed } - fn progress(&self) -> f32 { - self.time.as_secs_f32() / self.duration.as_secs_f32() + fn set_elapsed(&mut self, mut time: Duration) { + self.clock.set_elapsed(time); + time = self.clock.progress(); + + self.index = 0; + for tween in &mut self.tweens { + tween.set_elapsed(time); + time -= tween.elapsed(); + + if !time.is_zero() { + self.index += 1; + } + } } fn tick( @@ -655,7 +664,7 @@ impl Tweenable for Sequence { entity: Entity, event_writer: &mut EventWriter, ) -> TweenState { - self.time = (self.time + delta).min(self.duration); + self.clock.tick(delta); while self.index < self.tweens.len() { let tween = &mut self.tweens[self.index]; let tween_remaining = tween.duration().mul_f32(1.0 - tween.progress()); @@ -668,31 +677,22 @@ impl Tweenable for Sequence { self.index += 1; } - self.times_completed = 1; TweenState::Completed } - fn times_completed(&self) -> u32 { - self.times_completed - } - fn rewind(&mut self) { - self.time = Duration::ZERO; - self.index = 0; - self.times_completed = 0; - for tween in &mut self.tweens { - // or only first? + self.clock.reset(); + for tween in &mut self.tweens[..self.index] { tween.rewind(); } + self.index = 0; } } /// A collection of [`Tweenable`] executing in parallel. pub struct Tracks { tracks: Vec>, - duration: Duration, - time: Duration, - times_completed: u32, + clock: AnimClock, } impl Tracks { @@ -706,34 +706,29 @@ impl Tracks { .map(AsRef::as_ref) .map(Tweenable::duration) .max() - .unwrap(); + .unwrap_or(Duration::ZERO); Self { tracks, - duration, - time: Duration::ZERO, - times_completed: 0, + clock: AnimClock::new(duration), } } } impl Tweenable for Tracks { fn duration(&self) -> Duration { - self.duration + self.clock.duration } - fn set_progress(&mut self, progress: f32) { - self.times_completed = if progress >= 1. { 1 } else { 0 }; // not looping - let progress = progress.clamp(0., 1.); // not looping - let time_secs = self.duration.as_secs_f64() * progress as f64; - self.time = Duration::from_secs_f64(time_secs); - for tweenable in &mut self.tracks { - let progress = time_secs / tweenable.duration().as_secs_f64(); - tweenable.set_progress(progress as f32); - } + fn elapsed(&self) -> Duration { + self.clock.elapsed } - fn progress(&self) -> f32 { - self.time.as_secs_f32() / self.duration.as_secs_f32() + fn set_elapsed(&mut self, time: Duration) { + self.clock.set_elapsed(time); + let time = self.clock.progress(); + for tweenable in &mut self.tracks { + tweenable.set_elapsed(time); + } } fn tick( @@ -743,27 +738,19 @@ impl Tweenable for Tracks { entity: Entity, event_writer: &mut EventWriter, ) -> TweenState { - self.time = (self.time + delta).min(self.duration); - let mut any_active = false; - for tweenable in &mut self.tracks { - let state = tweenable.tick(delta, target, entity, event_writer); - any_active = any_active || (state == TweenState::Active); - } - if any_active { - TweenState::Active - } else { - self.times_completed = 1; - TweenState::Completed + if self.clock.state() == TweenState::Completed { + return TweenState::Completed; } - } + self.clock.tick(delta); - fn times_completed(&self) -> u32 { - self.times_completed + for tweenable in &mut self.tracks { + tweenable.tick(delta, target, entity, event_writer); + } + self.clock.state() } fn rewind(&mut self) { - self.time = Duration::ZERO; - self.times_completed = 0; + self.clock.reset(); for tween in &mut self.tracks { tween.rewind(); } @@ -802,18 +789,12 @@ impl Tweenable for Delay { self.timer.duration() } - fn set_progress(&mut self, progress: f32) { - // need to reset() to clear finished() unfortunately - self.timer.reset(); - self.timer.set_elapsed(Duration::from_secs_f64( - self.timer.duration().as_secs_f64() * progress as f64, - )); - // set_elapsed() does not update finished() etc. which we rely on - self.timer.tick(Duration::ZERO); + fn elapsed(&self) -> Duration { + self.timer.elapsed() } - fn progress(&self) -> f32 { - self.timer.percent() + fn set_elapsed(&mut self, time: Duration) { + self.timer.set_elapsed(time); } fn tick( @@ -831,14 +812,6 @@ impl Tweenable for Delay { } } - fn times_completed(&self) -> u32 { - if self.timer.finished() { - 1 - } else { - 0 - } - } - fn rewind(&mut self) { self.timer.reset(); } @@ -883,19 +856,25 @@ mod tests { Duration::from_secs_f32(1. / 120.), Duration::from_secs_f32(1. / 144.), Duration::from_secs_f32(1. / 240.), + Duration::from_secs(3), ]; let mut times_completed = 0; let mut total_duration = Duration::ZERO; for i in 0..10_000_000 { let tick = test_ticks[i % test_ticks.len()]; - times_completed += clock.tick(tick); + times_completed += clock.tick(tick) as u64; total_duration += tick; } + assert_eq!(total_duration, clock.elapsed); assert_eq!( - (total_duration.as_secs_f64() / duration.as_secs_f64()) as u32, - times_completed + total_duration.as_nanos() % duration.as_nanos(), + clock.progress().as_nanos() + ); + assert_eq!( + times_completed, + (total_duration.as_secs_f64() / duration.as_secs_f64()) as u64 ); } @@ -917,7 +896,7 @@ mod tests { // Create a linear tween over 1 second let mut tween = Tween::new( EaseMethod::Linear, - Duration::from_secs_f32(1.0), + Duration::from_secs(1), TransformPositionLens { start: Vec3::ZERO, end: Vec3::ONE, @@ -961,7 +940,7 @@ mod tests { // Loop over 2.2 seconds, so greater than one ping-pong loop let mut transform = Transform::default(); - 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) = @@ -1055,16 +1034,21 @@ mod tests { } RepeatCount::For(_) => panic!("Untested"), }; + let progress_for_factor = if progress > 0. && progress.fract() == 0. { + 1. + } else { + progress.fract() + }; let factor = if tweening_direction.is_backward() { direction = !direction; - 1. - progress + 1. - progress_for_factor } else { - progress + progress_for_factor }; let expected_translation = if direction.is_forward() { - Vec3::splat(progress) + Vec3::splat(progress_for_factor) } else { - Vec3::splat(1. - progress) + Vec3::splat(1. - progress_for_factor) }; println!( "Expected: progress={} factor={} times_completed={} direction={:?} state={:?} just_completed={} translation={:?}",