Skip to content
Snippets Groups Projects
tweenable.rs 51.9 KiB
Newer Older
                                            TweenState::Active
                                        } else {
                                            TweenState::Completed
                                        },
                                        just_completed,
                                    )
                                } else {
                                    let i5 = i % 5;
                                    let times_completed = i / 5;
                                    let i10 = i % 10;
                                    let direction = if i10 >= 5 {
                                        TweeningDirection::Backward
                                    } else {
                                        TweeningDirection::Forward
                                    };
                                    let just_completed = i5 == 0;
                                    (
                                        progress,
                                        times_completed,
                                        direction,
                                        if i < 10 {
                                            TweenState::Active
                                        } else {
                                            TweenState::Completed
                                        },
                                        just_completed,
                                    )
                                }
                            RepeatCount::Infinite => {
                                let progress = i as f32 * 0.2;
                                if *strategy == RepeatStrategy::Repeat {
                                    let times_completed = i / 5;
                                    let just_completed = i % 5 == 0;
                                    (
                                        progress,
                                        times_completed,
                                        TweeningDirection::Forward,
                                        TweenState::Active,
                                        just_completed,
                                    )
                                    let i5 = i % 5;
                                    let times_completed = i / 5;
                                    let i10 = i % 10;
                                    let direction = if i10 >= 5 {
                                        TweeningDirection::Backward
                                    } else {
                                        TweeningDirection::Forward
                                    };
                                    let just_completed = i5 == 0;
                                    (
                                        progress,
                                        times_completed,
                                        direction,
                                        TweenState::Active,
                                        just_completed,
                                    )
                                }
                            RepeatCount::For(_) => panic!("Untested"),
                        };
                    let factor = if tweening_direction.is_backward() {
                        direction = !direction;
                        1. - progress
                    } else {
                        progress
                    };
                    let expected_translation = if direction.is_forward() {
                        Vec3::splat(progress)
                    } else {
                        Vec3::splat(1. - progress)
                    };
                    println!(
                        "Expected: progress={} factor={} times_completed={} direction={:?} state={:?} just_completed={} translation={:?}",
                        progress, factor, times_completed, direction, expected_state, just_completed, expected_translation
                    );

                    // Tick the tween
                    let actual_state = {
                        let mut event_writer = event_writer_system_state.get_mut(&mut world);
                        tween.tick(
                            tick_duration,
                            &mut transform,
                            dummy_entity,
                            &mut event_writer,
                        )
                    };

                    // Propagate events
                    {
                        let mut events =
                            world.get_resource_mut::<Events<TweenCompleted>>().unwrap();
                        events.update();
                    }

                    // Check actual values
                    assert_eq!(tween.direction(), direction);
                    assert_eq!(actual_state, expected_state);
                    assert!(abs_diff_eq(tween.progress(), progress, 1e-5));
                    assert_eq!(tween.times_completed(), times_completed);
                    assert!(transform
                        .translation
                        .abs_diff_eq(expected_translation, 1e-5));
                    assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
                    let cb_mon = callback_monitor.lock().unwrap();
                    assert_eq!(cb_mon.invoke_count, times_completed as u64);
                    assert_eq!(cb_mon.last_reported_count, times_completed);
                    {
                        let mut event_reader = event_reader_system_state.get_mut(&mut world);
                        let event = event_reader.iter().next();
                        if just_completed {
                            assert!(event.is_some());
                            if let Some(event) = event {
                                assert_eq!(event.entity, dummy_entity);
                                assert_eq!(event.user_data, USER_DATA);
                            }
                        } else {
                            assert!(event.is_none());
                        }
                    }
                }

                // Rewind
                tween.rewind();
                assert_eq!(tween.direction(), *tweening_direction); // does not change
                assert!(abs_diff_eq(tween.progress(), 0., 1e-5));
                assert_eq!(tween.times_completed(), 0);

                // Dummy tick to update target
                let actual_state = {
                    let mut event_writer = event_writer_system_state.get_mut(&mut world);
                    tween.tick(
                assert_eq!(actual_state, TweenState::Active);
                let expected_translation = if tweening_direction.is_backward() {
                    Vec3::ONE
                } else {
                    Vec3::ZERO
                };
                assert!(transform
                    .translation
                    .abs_diff_eq(expected_translation, 1e-5));
                assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
                // Clear callback
                tween.clear_completed();
                assert!(tween.on_completed.is_none());
            }
    #[test]
    fn tween_dir() {
        let mut tween = Tween::new(
            EaseMethod::Linear,
            Duration::from_secs_f32(1.0),
            TransformPositionLens {
                start: Vec3::ZERO,
                end: Vec3::ONE,
            },
        );

        // Default
        assert_eq!(tween.direction(), TweeningDirection::Forward);
        assert!(abs_diff_eq(tween.progress(), 0.0, 1e-5));

        // no-op
        tween.set_direction(TweeningDirection::Forward);
        assert_eq!(tween.direction(), TweeningDirection::Forward);
        assert!(abs_diff_eq(tween.progress(), 0.0, 1e-5));

        // 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));

        // Progress-invariant
        tween.set_direction(TweeningDirection::Forward);
        tween.set_progress(0.3);
        assert!(abs_diff_eq(tween.progress(), 0.3, 1e-5));
        tween.set_direction(TweeningDirection::Backward);
        // progress is independent of direction
        assert!(abs_diff_eq(tween.progress(), 0.3, 1e-5));

        // Dummy world and event writer
        let mut world = World::new();
        world.insert_resource(Events::<TweenCompleted>::default());
        let mut event_writer_system_state: SystemState<EventWriter<TweenCompleted>> =
            SystemState::new(&mut world);

        // Progress always increases alongside the current direction
        let dummy_entity = Entity::from_raw(0);
        let mut transform = Transform::default();
        let mut event_writer = event_writer_system_state.get_mut(&mut world);
        tween.set_direction(TweeningDirection::Backward);
        assert!(abs_diff_eq(tween.progress(), 0.3, 1e-5));
        tween.tick(
            Duration::from_secs_f32(0.1),
            &mut transform,
            dummy_entity,
            &mut event_writer,
        );
        assert!(abs_diff_eq(tween.progress(), 0.4, 1e-5));
        assert!(transform.translation.abs_diff_eq(Vec3::splat(0.6), 1e-5));
    }

    /// Test ticking a sequence of tweens.
    #[test]
    fn seq_tick() {
        let tween1 = Tween::new(
            EaseMethod::Linear,
            Duration::from_secs_f32(1.0),
            TransformPositionLens {
                start: Vec3::ZERO,
                end: Vec3::ONE,
            },
        );
        let tween2 = Tween::new(
            EaseMethod::Linear,
            Duration::from_secs_f32(1.0),
            TransformRotationLens {
                start: Quat::IDENTITY,
Jerome Humbert's avatar
Jerome Humbert committed
                end: Quat::from_rotation_x(90_f32.to_radians()),
            },
        );
        let mut seq = tween1.then(tween2);
        let mut transform = Transform::default();

        // Dummy world and event writer
        let mut world = World::new();
        world.insert_resource(Events::<TweenCompleted>::default());
        let mut system_state: SystemState<EventWriter<TweenCompleted>> =
            SystemState::new(&mut world);
        let mut event_writer = system_state.get_mut(&mut world);

        for i in 1..=16 {
            let state = seq.tick(
                Duration::from_secs_f32(0.2),
                &mut transform,
                Entity::from_raw(0),
                &mut event_writer,
Jerome Humbert's avatar
Jerome Humbert committed
            if i < 5 {
                assert_eq!(state, TweenState::Active);
                let r = i as f32 * 0.2;
                assert_eq!(transform, Transform::from_translation(Vec3::splat(r)));
Jerome Humbert's avatar
Jerome Humbert committed
            } else if i < 10 {
                assert_eq!(state, TweenState::Active);
Jerome Humbert's avatar
Jerome Humbert committed
                let alpha_deg = (18 * (i - 5)) as f32;
                assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
                assert!(transform
                    .rotation
                    .abs_diff_eq(Quat::from_rotation_x(alpha_deg.to_radians()), 1e-5));
            } else {
                assert_eq!(state, TweenState::Completed);
                assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
                assert!(transform
                    .rotation
Jerome Humbert's avatar
Jerome Humbert committed
                    .abs_diff_eq(Quat::from_rotation_x(90_f32.to_radians()), 1e-5));
    /// Test crossing tween boundaries in one tick.
    #[test]
    fn seq_tick_boundaries() {
        let mut seq = Sequence::new((0..3).map(|i| {
            Tween::new(
                EaseMethod::Linear,
                Duration::from_secs(1),
                TransformPositionLens {
                    start: Vec3::splat(i as f32),
                    end: Vec3::splat((i + 1) as f32),
                },
            )
            .with_repeat_count(RepeatCount::Finite(1))
        }));
        let mut transform = Transform::default();

        // Dummy world and event writer
        let mut world = World::new();
        world.insert_resource(Events::<TweenCompleted>::default());
        let mut system_state: SystemState<EventWriter<TweenCompleted>> =
            SystemState::new(&mut world);
        let mut event_writer = system_state.get_mut(&mut world);

        // Tick halfway through the first tween, then in one tick:
        // - Finish the first tween
        // - Start and finish the second tween
        // - Start the third tween
        for delta in [0.5, 2.0] {
            seq.tick(
                Duration::from_secs_f32(delta),
                &mut transform,
                Entity::from_raw(0),
                &mut event_writer,
            );
        }
        assert_eq!(seq.index(), 2);
        assert!(transform.translation.abs_diff_eq(Vec3::splat(2.5), 1e-5));
    }

    /// Sequence::new() and various Sequence-specific methods
    #[test]
    fn seq_iter() {
        let mut seq = Sequence::new((1..5).map(|i| {
            Tween::new(
                EaseMethod::Linear,
                Duration::from_secs_f32(0.2 * i as f32),
                TransformPositionLens {
                    start: Vec3::ZERO,
                    end: Vec3::ONE,
                },
            )
        }));

        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));
            progress += 0.25;
            seq.set_progress(progress);
            assert_eq!(seq.times_completed(), if i == 4 { 1 } else { 0 });
        }

        seq.rewind();
        assert_eq!(seq.progress(), 0.);
        assert_eq!(seq.times_completed(), 0);
    }

    /// Test ticking parallel tracks of tweens.
    #[test]
    fn tracks_tick() {
        let tween1 = Tween::new(
            EaseMethod::Linear,
            Duration::from_secs_f32(1.),
            TransformPositionLens {
                start: Vec3::ZERO,
                end: Vec3::ONE,
            },
        );
        let tween2 = Tween::new(
            EaseMethod::Linear,
Jerome Humbert's avatar
Jerome Humbert committed
            Duration::from_secs_f32(0.8), // shorter
            TransformRotationLens {
                start: Quat::IDENTITY,
Jerome Humbert's avatar
Jerome Humbert committed
                end: Quat::from_rotation_x(90_f32.to_radians()),
            },
        );
        let mut tracks = Tracks::new([tween1, tween2]);
        assert_eq!(tracks.duration(), Duration::from_secs_f32(1.)); // max(1., 0.8)

        let mut transform = Transform::default();

        // Dummy world and event writer
        let mut world = World::new();
        world.insert_resource(Events::<TweenCompleted>::default());
        let mut system_state: SystemState<EventWriter<TweenCompleted>> =
            SystemState::new(&mut world);
        let mut event_writer = system_state.get_mut(&mut world);

        for i in 1..=6 {
            let state = tracks.tick(
                Duration::from_secs_f32(0.2),
                &mut transform,
                Entity::from_raw(0),
                &mut event_writer,
Jerome Humbert's avatar
Jerome Humbert committed
            if i < 5 {
                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);
Jerome Humbert's avatar
Jerome Humbert committed
                let alpha_deg = 22.5 * i as f32;
                assert!(transform.translation.abs_diff_eq(Vec3::splat(r), 1e-5));
                assert!(transform
                    .rotation
                    .abs_diff_eq(Quat::from_rotation_x(alpha_deg.to_radians()), 1e-5));
            } else {
                assert_eq!(state, TweenState::Completed);
                assert_eq!(tracks.times_completed(), 1);
                assert!((tracks.progress() - 1.).abs() < 1e-5);
                assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
                assert!(transform
                    .rotation
Jerome Humbert's avatar
Jerome Humbert committed
                    .abs_diff_eq(Quat::from_rotation_x(90_f32.to_radians()), 1e-5));

        tracks.rewind();
        assert_eq!(tracks.times_completed(), 0);
        assert!(tracks.progress().abs() < 1e-5);

        tracks.set_progress(0.9);
        assert!((tracks.progress() - 0.9).abs() < 1e-5);
        // tick to udpate state (set_progress() does not update state)
        let state = tracks.tick(
            Duration::from_secs_f32(0.),
            &mut transform,
            Entity::from_raw(0),
            &mut event_writer,
        );
        assert_eq!(state, TweenState::Active);
        assert_eq!(tracks.times_completed(), 0);

        tracks.set_progress(3.2);
        assert!((tracks.progress() - 1.).abs() < 1e-5);
        // tick to udpate state (set_progress() does not update state)
        let state = tracks.tick(
            Duration::from_secs_f32(0.),
            &mut transform,
            Entity::from_raw(0),
            &mut event_writer,
        );
        assert_eq!(state, TweenState::Completed);
        assert_eq!(tracks.times_completed(), 1); // no looping

        tracks.set_progress(-0.5);
        assert!(tracks.progress().abs() < 1e-5);
        // tick to udpate state (set_progress() does not update state)
        let state = tracks.tick(
            Duration::from_secs_f32(0.),
            &mut transform,
            Entity::from_raw(0),
            &mut event_writer,
        );
        assert_eq!(state, TweenState::Active);
        assert_eq!(tracks.times_completed(), 0); // no looping
    }

    /// Test ticking a delay.
    #[test]
    fn delay_tick() {
        let duration = Duration::from_secs_f32(1.0);
        let mut delay = Delay::new(duration);
        {
            let tweenable: &dyn Tweenable<Transform> = &delay;
            assert_eq!(tweenable.duration(), duration);
            assert!(tweenable.progress().abs() < 1e-5);
        }

        let mut transform = Transform::default();

        // Dummy world and event writer
        let mut world = World::new();
        world.insert_resource(Events::<TweenCompleted>::default());
        let mut system_state: SystemState<EventWriter<TweenCompleted>> =
            SystemState::new(&mut world);
        let mut event_writer = system_state.get_mut(&mut world);

        for i in 1..=6 {
            let state = delay.tick(
                Duration::from_secs_f32(0.2),
                &mut transform,
                Entity::from_raw(0),
                &mut event_writer,
            );
            {
                let tweenable: &dyn Tweenable<Transform> = &delay;
                if i < 5 {
                    assert_eq!(state, TweenState::Active);
                    let r = i as f32 * 0.2;
                    assert!((tweenable.progress() - r).abs() < 1e-5);
                } else {
                    assert_eq!(state, TweenState::Completed);
                    assert!((tweenable.progress() - 1.).abs() < 1e-5);
                }
            }
        }

    #[test]
    #[should_panic]
    fn delay_zero_duration_panics() {
        let _ = Delay::new(Duration::ZERO);
    }