diff --git a/Cargo.lock b/Cargo.lock index 1b4c568..b27adbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.11" @@ -20,6 +31,15 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "anymap" +version = "1.0.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1f8f5a6f3d50d89e3797d7593a50f96bb2aaa20ca0cc7be1fb673232c91d72" +dependencies = [ + "hashbrown 0.12.3", +] + [[package]] name = "arrayvec" version = "0.7.4" @@ -130,6 +150,17 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "guillotiere" version = "0.6.2" @@ -140,13 +171,22 @@ dependencies = [ "svg_fmt", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash", + "ahash 0.8.11", "allocator-api2", ] @@ -163,14 +203,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] name = "interpoli" version = "0.1.0" dependencies = [ - "hashbrown", + "anymap", + "hashbrown 0.14.5", "keyframe", "kurbo", "peniko", @@ -492,6 +533,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi-util" version = "0.1.9" diff --git a/Cargo.toml b/Cargo.toml index 3ae7531..e6ffba3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ targets = [] [dependencies] hashbrown = "0.14.5" +anymap = { version = "1.0.0-beta.2", default-features = false, features = ["hashbrown"] } keyframe = { version = "1.1.1", default-features = false } kurbo = { version = "0.11", default-features = false } peniko = { version = "0.1.1", default-features = false } diff --git a/src/lib.rs b/src/lib.rs index eaaa6d4..6505458 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,15 +14,20 @@ mod composition; mod spline; mod value; -#[cfg(feature = "vello")] -mod render; - +#[macro_use] +pub mod timeline; pub mod animated; pub mod fixed; +pub use timeline::{Framerate, Keyframe, Sequence, StaticTimeline, Timecode, Timeline}; + +#[cfg(feature = "vello")] +mod render; + pub use composition::{ Composition, Content, Draw, Geometry, GroupTransform, Layer, Mask, Matte, Shape, }; + pub use value::{Animated, Easing, EasingHandle, Time, Tween, Value, ValueRef}; #[cfg(feature = "vello")] @@ -86,3 +91,578 @@ impl Default for Transform { Self::Fixed(Affine::IDENTITY) } } + +// Syntax Tests. + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_macro() { + println!("tcode_macro: {:?}", tcode_hmsf!(1:23:45:01).as_string()); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_set_hms() { + println!("tcode_set_hms: {:?}", tcode_hms!(98:76:54).hms_as_string()); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_with_framerate() { + println!( + "tcode_with_framerate: {:?}", + tcode_hmsf_framerate!(00:01:02:56, Framerate::Fixed(20.0)).as_string() + ); +} + +// Assert Tests. + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_macro_overflow() { + let time = tcode_hmsf!(99:99:99:99); + + println!( + "tcode_macro_overflow: {:?}", + tcode_hmsf!(99:99:99:99).as_string() + ); + assert!(time.is_equals_to_hmsf(&tcode_hmsf!(100:40:39:99))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_full_24fps_second() { + let mut time = tcode_hmsf_framerate!(00:00:00:00, Framerate::Fixed(24.0)); + + for _i in 0..24 { + time.next_frame(); + } + + println!("tcode_full_24f_second: {:?}", time.as_string()); + assert!(time.is_equals_to_hmsf(&tcode_hmsf!(00:00:01:00))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_full_24fps_minute() { + let mut time = tcode_hmsf_framerate!(00:00:00:00, Framerate::Fixed(24.0)); + + for _i in 0..60 { + time.next_second(); + } + + println!("tcode_full_24f_minute: {:?}", time.as_string()); + assert!(time.is_equals_to_hmsf(&tcode_hmsf!(00:01:00:00))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_full_24fps_hour() { + let mut time = tcode_hmsf_framerate!(00:00:00:00, Framerate::Fixed(24.0)); + + for _i in 0..60 { + time.next_minute(); + } + + println!("tcode_full_24f_hour: {:?}", time.as_string()); + assert!(time.is_equals_to_hmsf(&tcode_hmsf!(01:00:00:00))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_set_by_duration() { + use core::time::Duration; + + let mut time = tcode_hmsf_framerate!(00:00:10:00, Framerate::Fixed(23.97)); + + time.set_by_duration(Duration::from_secs(2)); + + println!("tcode_set_by_duration: {:?}", time.as_string()); + assert!(time.is_equals_to_hmsf(&tcode_hmsf!(00:00:02:00))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_set_by_timestamp() { + let mut time = tcode_hmsf_framerate!(00:00:10:00, Framerate::Fixed(23.97)); + + time.set_by_timestamp(tcode_hmsf!(00:05:00:00)); + + println!("tcode_set_by_timestamp: {:?}", time.as_string()); + assert!(time.is_equals_to_hmsf(&tcode_hmsf!(00:05:00:00))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_add_by_duration() { + use core::time::Duration; + + let mut time = tcode_hmsf_framerate!(00:00:00:00, Framerate::Fixed(24.0)); + + time.add_by_duration(Duration::from_millis(999)); + + println!("tcode_add_by_duration: {:?}", time.as_string()); + assert!(time.is_equals_to_hmsf(&tcode_hmsf!(00:00:00:23))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_add_by_timestamp() { + let mut time = tcode_hmsf_framerate!(00:00:05:00, Framerate::Fixed(24.0)); + + time.add_by_timestamp(tcode_hmsf!(00:00:05:00)); + + println!("tcode_add_by_timestamp: {:?}", time.as_string()); + assert!(time.is_equals_to_hmsf(&tcode_hmsf!(00:00:10:00))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_sub_by_duration() { + use core::time::Duration; + + let mut time = tcode_hmsf_framerate!(01:00:00:00, Framerate::Fixed(24.0)); + + time.sub_by_duration(Duration::from_secs(1800)); + + println!("tcode_sub_by_duration: {:?}", time.as_string()); + assert!(time.is_equals_to_hmsf(&tcode_hmsf!(00:30:00:00))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_sub_by_timestamp() { + let mut time = tcode_hmsf_framerate!(00:01:00:00, Framerate::Fixed(24.0)); + + time.sub_by_timestamp(tcode_hmsf!(00:00:20:00)); + + println!("tcode_sub_by_timestamp: {:?}", time.as_string()); + assert!(time.is_equals_to_hmsf(&tcode_hmsf!(00:00:40:00))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_ntsc_tv() { + use core::time::Duration; + + let mut time = tcode_hmsf_framerate!(00:00:00:00, Framerate::Fixed(23.97)); + + time.add_by_duration(Duration::from_millis(2000)); + + println!("tcode_ntsc_tv: {:?}", time.as_string()); + assert!(time.is_equals_to_hmsf(&tcode_hmsf!(00:00:02:00))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_high_fps() { + use core::time::Duration; + + let mut time = tcode_hmsf_framerate!(00:00:00:00, Framerate::Fixed(1000.0)); + + time.add_by_duration(Duration::from_millis(2000)); + + println!("tcode_high_fps: {:?}", time.as_string()); + assert!(time.is_equals_to_hmsf(&tcode_hmsf!(00:00:02:00))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_framerate_standard_that_doesnt_exist() { + use core::time::Duration; + + let mut time = tcode_hmsf_framerate!(00:00:00:00, Framerate::Fixed(159.3947)); + + time.add_by_duration(Duration::from_millis(2000)); + + println!( + "tcode_framerate_standard_that_doesnt_exist: {:?}", + time.as_string() + ); + assert!(time.is_equals_to_hmsf(&tcode_hmsf!(00:00:02:00))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_as_nanoseconds() { + use core::time::Duration; + + let time = tcode_hmsf_framerate!(00:00:09:00, Framerate::Fixed(30.0)); + let dur = Duration::from_secs(9); + + println!( + "tcode_as_nanoseconds: {:?} ({:?}) = {:?}", + time.as_nanoseconds(), + time.as_string(), + dur.as_nanos() + ); + assert!(time.as_nanoseconds() == dur.as_nanos() as isize); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_get_lerp_time_between() { + let time = tcode_hmsf_framerate!(00:00:00:12, Framerate::Fixed(24.0)); + + let begin = tcode_hmsf!(00:00:00:00); + let end = tcode_hmsf!(00:00:00:24); + + let result = time.get_lerp_time_between(&begin, &end); + + println!("tcode_get_lerp_time_between: {:?}", result); + assert!(result == 0.5); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tcode_lerp_fixed_vs_inter() { + let time_fixed = tcode_full!(00:00:00:00:500_000_000, Framerate::Fixed(1.0)); + let time_inter = tcode_full!(00:00:00:00:500_000_000, Framerate::Interpolated(1.0)); + + let begin = tcode_hmsf!(00:00:00:00); + let end = tcode_hmsf!(00:00:00:01); + + let fixed_result = time_fixed.get_lerp_time_between(&begin, &end); + let inter_result = time_inter.get_lerp_time_between(&begin, &end); + + println!( + "tcode_lerp_fixed_vs_inter: fixed {:?} | interpolated {:?}", + fixed_result, inter_result + ); + assert!(fixed_result == 0.0 && inter_result == 0.5); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tline_new() { + let timeline = Timeline::new(Framerate::Fixed(24.0)); + + assert!(timeline.time().is_equals_to_hmsf(&tcode_hmsf!(00:00:00:00))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tline_set_by_timestamp() { + let mut timeline = Timeline::new(Framerate::Fixed(24.0)); + + timeline.set_by_timestamp(tcode_hmsf!(00:00:05:00)); + + assert!(timeline.time().is_equals_to_hmsf(&tcode_hmsf!(00:00:05:00))); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tline_new_integer_sequences() { + let mut timeline = Timeline::new(Framerate::Fixed(24.0)); + + let sequence_one: &mut Sequence = timeline.new_sequence("sequence_one").unwrap(); + + assert!(sequence_one + .add_keyframe_at_timestamp(Keyframe { value: 3.0 }, &tcode_hmsf!(00:00:05:00)) + .is_some()); + + let sequence_two: &mut Sequence = timeline.new_sequence("sequence_two").unwrap(); + + assert!(sequence_two + .add_keyframe_at_timestamp(Keyframe { value: 6.0 }, &tcode_hmsf!(00:00:10:00)) + .is_some()); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tline_new_kurbo_sequences() { + use kurbo::Vec2; + + #[cfg(feature = "std")] + use std::time::Instant; + + let mut timeline = Timeline::new(Framerate::Fixed(24.0)); + + let sequence: &mut Sequence = timeline.new_sequence("sequence").unwrap(); + + sequence.add_keyframes_at_timestamp(vec![ + ( + Keyframe { + value: Vec2::new(0.0, 1.0), + }, + &tcode_hmsf!(00:00:01:00), + ), + ( + Keyframe { + value: Vec2::new(1.0, 1.0), + }, + &tcode_hmsf!(00:00:02:00), + ), + ( + Keyframe { + value: Vec2::new(1.0, 2.0), + }, + &tcode_hmsf!(00:00:03:00), + ), + ]); + + #[cfg(feature = "std")] + let instant = Instant::now(); + + assert!(sequence + .get_keyframe_at_timestamp(&tcode_hmsf!(00:00:02:00)) + .is_some()); + + #[cfg(feature = "std")] + println!("tline_new_kurbo_sequences: {:?}", instant.elapsed()); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn static_tline_new_kurbo_sequences() { + use kurbo::Vec2; + + #[cfg(feature = "std")] + use std::time::Instant; + + let mut timeline: StaticTimeline = StaticTimeline::new(Framerate::Fixed(24.0)); + + let sequence = timeline.new_sequence("sequence").unwrap(); + + sequence.add_keyframes_at_timestamp(vec![ + ( + Keyframe { + value: Vec2::new(0.0, 1.0), + }, + &tcode_hmsf!(00:00:01:00), + ), + ( + Keyframe { + value: Vec2::new(1.0, 1.0), + }, + &tcode_hmsf!(00:00:02:00), + ), + ( + Keyframe { + value: Vec2::new(1.0, 2.0), + }, + &tcode_hmsf!(00:00:03:00), + ), + ]); + + #[cfg(feature = "std")] + let instant = Instant::now(); + + assert!(sequence + .get_keyframe_at_timestamp(&tcode_hmsf!(00:00:02:00)) + .is_some()); + + #[cfg(feature = "std")] + println!("tline_new_kurbo_sequences: {:?}", instant.elapsed()); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tline_nesting() { + use kurbo::Vec2; + + let mut main = Timeline::new(Framerate::Fixed(24.0)); + + let main_seq: &mut Sequence = main.new_sequence("main_seq").unwrap(); + main_seq.add_keyframe_at_timestamp(Keyframe { value: 0.0 }, &tcode_hmsf!(00:00:02:00)); + + let mut child = Timeline::new(Framerate::Fixed(24.0)); + + let child_seq: &mut Sequence = child.new_sequence("child_seq").unwrap(); + child_seq.add_keyframe_at_timestamp( + Keyframe { + value: Vec2::new(0.0, 1.0), + }, + &tcode_hmsf!(00:00:02:00), + ); + + main.add_child(child); + + assert!(main.children().len() == 1); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tline_stress_test() { + #[cfg(feature = "std")] + use std::time::Instant; + + let mut timeline = Timeline::new(Framerate::Fixed(24.0)); + let framerate = *timeline.framerate(); + + let sequence_one: &mut Sequence = timeline.new_sequence("stress_test").unwrap(); + + for i in 0..1_000_000 { + sequence_one.add_keyframe_at_timestamp( + Keyframe { value: i as f64 }, + &tcode_hmsf_framerate!(00:00:i:00, framerate), + ); + } + + #[cfg(feature = "std")] + let instant = Instant::now(); + + let keyframe = sequence_one.get_keyframe_at_timestamp(&tcode_hmsf!(00:15:00:00)); + + #[cfg(feature = "std")] + println!("tline_stress_test: {:?}", instant.elapsed()); + + assert!(keyframe.is_some()); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn static_tline_stress_test() { + #[cfg(feature = "std")] + use std::time::Instant; + + let mut timeline: StaticTimeline = StaticTimeline::new(Framerate::Fixed(24.0)); + let framerate = *timeline.framerate(); + + let sequence_one = timeline.new_sequence("stress_test").unwrap(); + + for i in 0..1_000_000 { + sequence_one.add_keyframe_at_timestamp( + Keyframe { value: i as f64 }, + &tcode_hmsf_framerate!(00:00:i:00, framerate), + ); + } + + #[cfg(feature = "std")] + let instant = Instant::now(); + + let keyframe = sequence_one.get_keyframe_at_timestamp(&tcode_hmsf!(00:15:00:00)); + + #[cfg(feature = "std")] + println!("static_tline_stress_test: {:?}", instant.elapsed()); + + assert!(keyframe.is_some()); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn sequence_add_keyframe_at_timestamp() { + let mut seq = Sequence::::new(); + + assert!(seq + .add_keyframe_at_timestamp(Keyframe { value: 0.5 }, &tcode_hmsf!(00:00:05:00)) + .is_some()); + assert!(seq + .add_keyframe_at_timestamp(Keyframe { value: 1.0 }, &tcode_hmsf!(00:00:10:00)) + .is_some()); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn sequence_get_keyframes_between() { + let mut seq = Sequence::::new(); + + assert!(seq + .add_keyframe_at_timestamp(Keyframe { value: 0.5 }, &tcode_hmsf!(00:00:05:00)) + .is_some()); + assert!(seq + .add_keyframe_at_timestamp(Keyframe { value: 1.0 }, &tcode_hmsf!(00:02:00:00)) + .is_some()); + + let keyframes = seq.get_keyframes_between( + &tcode_hmsf!(00:00:00:00), + &tcode_hmsf!(00:05:00:00), + &Framerate::Fixed(24.0), + ); + + assert!(keyframes.len() == 2); + + println!("sequence_get_keyframes_between: {:?}", keyframes); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn sequence_find_first_keyframe_after_timestamp() { + #[cfg(feature = "std")] + use std::time::Instant; + + let mut seq = Sequence::::new(); + + assert!(seq + .add_keyframe_at_timestamp(Keyframe { value: 0.5 }, &tcode_hmsf!(00:00:05:00)) + .is_some()); + assert!(seq + .add_keyframe_at_timestamp(Keyframe { value: 1.0 }, &tcode_hmsf!(00:00:06:00)) + .is_some()); + + #[cfg(feature = "std")] + let instant = Instant::now(); + + let keyframe = + seq.find_first_keyframe_after_timestamp(&tcode_hmsf!(00:00:05:00), &Framerate::Fixed(24.0)); + + #[cfg(feature = "std")] + println!("time: {:?}", instant.elapsed()); + + assert!(keyframe.is_some()); + + println!( + "sequence_find_first_keyframe_after_timestamp: {:?}", + keyframe + ); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tline_basic_linear_animation() { + let mut timeline = Timeline::new(Framerate::Fixed(60.0)); + + let sequence: &mut Sequence = timeline.new_sequence("animation_sequence").unwrap(); + + sequence.add_keyframe_at_timestamp(Keyframe { value: 0.0 }, &tcode_hmsf!(00:00:00:00)); + sequence.add_keyframe_at_timestamp(Keyframe { value: 1.0 }, &tcode_hmsf!(00:00:01:00)); + + timeline.add_by_timestamp(tcode_hmsf!(00:00:00:30)); + + let result: f64 = timeline.tween_by_name("animation_sequence"); + + println!("tline_animation_test_one: {:?}", result); +} + +#[test] +#[allow(clippy::zero_prefixed_literal)] +fn tline_animation_fixed_vs_inter() { + let mut timeline_one = Timeline::new(Framerate::Fixed(1.0)); + let mut timeline_two = Timeline::new(Framerate::Interpolated(1.0)); + + let sequence_one: &mut Sequence = timeline_one.new_sequence("sequence").unwrap(); + let sequence_two: &mut Sequence = timeline_two.new_sequence("sequence").unwrap(); + + sequence_one.add_keyframe_at_timestamp(Keyframe { value: 0.0 }, &tcode_hmsf!(00:00:00:00)); + sequence_one.add_keyframe_at_timestamp(Keyframe { value: 1.0 }, &tcode_hmsf!(00:00:00:01)); + + println!("tline_animation_fixed_vs_inter (fixed):"); + println!("========================"); + + for i in 0..10 { + timeline_one.add_by_timestamp(tcode_full!(00:00:00:00:100_000_000, Framerate::Timestamp)); + println!( + "(Nanoframe {:?}): {:?}", + (i + 1) * 100_000_000, + timeline_one.tween_by_name::("sequence") + ); + } + + sequence_two.add_keyframe_at_timestamp(Keyframe { value: 0.0 }, &tcode_hmsf!(00:00:00:00)); + sequence_two.add_keyframe_at_timestamp(Keyframe { value: 1.0 }, &tcode_hmsf!(00:00:00:01)); + + println!("========================"); + println!("tline_animation_fixed_vs_inter (inter):"); + println!("========================"); + + for i in 0..10 { + timeline_two.add_by_timestamp(tcode_full!(00:00:00:00:100_000_000, Framerate::Timestamp)); + println!( + "(Nanoframe {:?}): {:?}", + (i + 1) * 100_000_000, + timeline_two.tween_by_name::("sequence"), + ); + } + + println!("========================"); +} diff --git a/src/timeline.rs b/src/timeline.rs new file mode 100644 index 0000000..e9cb10f --- /dev/null +++ b/src/timeline.rs @@ -0,0 +1,1199 @@ +// Copyright 2024 the Interpoli Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use crate::{Easing, Tween}; +use alloc::{ + collections::btree_map::BTreeMap, + fmt::Debug, + format, + string::{String, ToString}, + vec::Vec, +}; +use anymap::hashbrown::AnyMap; +use core::time::Duration; +use hashbrown::HashMap; + +#[derive(Copy, Debug, Clone)] +pub enum Framerate { + Timestamp, + Fixed(f64), + Interpolated(f64), +} + +impl Framerate { + pub fn as_string(&self) -> String { + match self { + Framerate::Timestamp => 0.0_f64.to_string(), + Framerate::Fixed(f) | Framerate::Interpolated(f) => f.to_string(), + } + } + + pub fn as_f64(&self) -> f64 { + match self { + Framerate::Timestamp => 0.0_f64, + Framerate::Fixed(f) | Framerate::Interpolated(f) => *f, + } + } + + pub fn is_timestamp(&self) -> bool { + matches!(self, Framerate::Timestamp) + } + + pub fn is_interpolated(&self) -> bool { + matches!(self, Framerate::Interpolated(_s)) + } +} + +#[derive(Debug, Clone)] +pub struct Timecode { + hours: isize, + minutes: isize, + seconds: isize, + frames: isize, + nanoframes: isize, + framerate: Framerate, +} + +#[allow(unused_macros)] +#[macro_export] +macro_rules! tcode_hmsf { + ($h:tt:$m:tt:$s:tt:$f:tt) => { + Timecode::new($h, $m, $s, $f) + }; +} + +#[allow(unused_macros)] +#[macro_export] +macro_rules! tcode_hmsf_framerate { + ($h:tt:$m:tt:$s:tt:$f:tt, $fr:expr) => { + Timecode::new_with_framerate($h, $m, $s, $f, 0, $fr) + }; +} + +#[allow(unused_macros)] +#[macro_export] +macro_rules! tcode_hms { + ($h:tt:$m:tt:$s:tt) => { + Timecode::new($h, $m, $s, 0) + }; +} + +#[allow(unused_macros)] +#[macro_export] +macro_rules! tcode_full { + ($h:tt:$m:tt:$s:tt:$f:tt:$nf:tt, $fr:expr) => { + Timecode::new_with_framerate($h, $m, $s, $f, $nf, $fr) + }; +} + +impl Default for Timecode { + fn default() -> Self { + Timecode::new_with_framerate(0, 0, 0, 0, 0, Framerate::Timestamp) + } +} + +impl Timecode { + pub fn new(h: isize, m: isize, s: isize, f: isize) -> Self { + Timecode::new_with_framerate(h, m, s, f, 0, Framerate::Timestamp) + } + + pub fn new_with_framerate( + h: isize, + m: isize, + s: isize, + f: isize, + nf: isize, + fr: Framerate, + ) -> Self { + let mut t = Self { + hours: h, + minutes: m, + seconds: s, + frames: f, + nanoframes: nf, + framerate: fr, + }; + + t.correct_overflow(); + + t + } + + #[inline] + pub fn hours(&self) -> &isize { + &self.hours + } + + #[inline] + pub fn minutes(&self) -> &isize { + &self.minutes + } + + #[inline] + pub fn seconds(&self) -> &isize { + &self.seconds + } + + #[inline] + pub fn frames(&self) -> &isize { + &self.frames + } + + #[inline] + pub fn nanoframes(&self) -> &isize { + &self.nanoframes + } + + #[inline] + pub fn framerate(&self) -> &Framerate { + &self.framerate + } + + #[inline] + pub fn set_framerate(&mut self, fr: Framerate) { + self.framerate = fr; + } + + pub fn correct_with_framerate(&mut self, fr: Framerate) -> &Self { + self.framerate = fr; + self.correct_overflow(); + self.correct_underflow(); + self + } + + fn correct_overflow(&mut self) { + let framerate = self.framerate.as_f64(); + + while self.nanoframes > 999_999_999 { + self.frames += 1; + self.nanoframes -= 1_000_000_000; + } + + if framerate != 0.0 { + while self.frames >= framerate as isize { + self.seconds += 1; + self.frames -= framerate as isize; + } + } + + while self.seconds > 59 { + self.minutes += 1; + self.seconds -= 60; + } + + while self.minutes > 59 { + self.hours += 1; + self.minutes -= 60; + } + } + + fn correct_underflow(&mut self) { + let framerate = self.framerate.as_f64(); + + while self.nanoframes < 0 { + self.frames -= 1; + self.nanoframes += 1_000_000_000; + } + + if framerate != 0.0 { + while self.frames < 0 { + self.seconds -= 1; + self.frames += framerate as isize; + } + } + + while self.seconds < 0 { + self.minutes -= 1; + self.seconds += 60; + } + + while self.minutes < 0 { + self.hours -= 1; + self.minutes += 60; + } + } + + pub fn as_string(&self) -> String { + format!( + "{:02}:{:02}:{:02}:{:02} ({:?})", + self.hours, + self.minutes, + self.seconds, + self.frames, + self.framerate.as_f64() + ) + } + + pub fn full_as_string(&self) -> String { + format!( + "{:02}:{:02}:{:02}:{:02}:{:09} ({:?})", + self.hours, + self.minutes, + self.seconds, + self.frames, + self.nanoframes, + self.framerate.as_f64() + ) + } + + pub fn hms_as_string(&self) -> String { + format!("{:02}:{:02}:{:02}", self.hours, self.minutes, self.seconds) + } + + // Add/Next + + #[inline] + pub fn next_frame(&mut self) { + self.frames += 1; + self.correct_overflow(); + } + + #[inline] + pub fn next_second(&mut self) { + self.seconds += 1; + self.correct_overflow(); + } + + #[inline] + pub fn next_minute(&mut self) { + self.minutes += 1; + self.correct_overflow(); + } + + #[inline] + pub fn next_hour(&mut self) { + self.hours += 1; + } + + pub fn add_by_duration(&mut self, d: Duration) { + let nanos = d.as_nanos() as isize; + self.nanoframes += nanos * self.framerate.as_f64() as isize; + + self.correct_overflow(); + } + + pub fn add_by_timestamp(&mut self, t: Timecode) { + let nanos = t.as_nanoseconds_with_framerate(&self.framerate, false); + self.nanoframes += nanos * self.framerate.as_f64() as isize; + + self.correct_overflow(); + } + + // Sub/Back + + #[inline] + pub fn back_frame(&mut self) { + self.frames -= 1; + self.correct_underflow(); + } + + #[inline] + pub fn back_second(&mut self) { + self.seconds -= 1; + self.correct_underflow(); + } + + #[inline] + pub fn back_minute(&mut self) { + self.minutes -= 1; + self.correct_underflow(); + } + + #[inline] + pub fn back_hour(&mut self) { + self.hours -= 1; + } + + pub fn sub_by_duration(&mut self, d: Duration) { + let nanos = d.as_nanos() as isize; + self.nanoframes -= nanos * self.framerate.as_f64() as isize; + + self.correct_underflow(); + } + + pub fn sub_by_timestamp(&mut self, t: Timecode) { + let nanos = t.as_nanoseconds_with_framerate(&self.framerate, false); + self.nanoframes -= nanos * self.framerate.as_f64() as isize; + + self.correct_underflow(); + } + + pub fn reset(&mut self) { + self.nanoframes = 0; + self.frames = 0; + self.seconds = 0; + self.minutes = 0; + self.hours = 0; + } + + pub fn set_by_duration(&mut self, d: Duration) { + self.reset(); + self.add_by_duration(d); + } + + pub fn set_by_timestamp(&mut self, t: Timecode) { + self.reset(); + self.add_by_timestamp(t); + } + + pub fn as_nanoseconds(&self) -> isize { + self.as_nanoseconds_with_framerate(&self.framerate, false) + } + + pub fn as_nanoseconds_with_framerate(&self, fr: &Framerate, for_tweening: bool) -> isize { + let mut nanos: isize = 0; + let framerate = if fr.as_f64() != 0.0 { + fr.as_f64() + } else { + // If it's a timestamp, cancel the division. + 1.0 + }; + + if matches!(fr, Framerate::Interpolated(_f)) || !for_tweening { + nanos += self.nanoframes / framerate as isize; + } + + nanos += ((self.frames as f64 / framerate) * 1000000000.0) as isize; + nanos += self.seconds * (1e+9 as isize); + nanos += self.minutes * (6e+10 as isize); + nanos += self.hours * (3.6e+12 as isize); + + nanos + } + + // Checks + + pub fn is_equals_to_hmsf(&self, t: &Timecode) -> bool { + self.frames == t.frames + && self.seconds == t.seconds + && self.minutes == t.minutes + && self.hours == t.hours + } + + // Utils + + pub fn get_lerp_time_between(&self, begin: &Timecode, end: &Timecode) -> f64 { + // TODO: There should've be a much efficient way to do this. + // But i think it'll work for now... + + let t = self.as_nanoseconds_with_framerate(&self.framerate, true); + let a = begin.as_nanoseconds_with_framerate(&self.framerate, true); + let b = end.as_nanoseconds_with_framerate(&self.framerate, true); + + let a_f64 = a as f64; + let b_f64 = b as f64 - a_f64; + let t_f64 = t as f64 - a_f64; + + let lerp = 0.0 + (b_f64 - 0.0) * (t_f64 / b_f64); + + lerp / b_f64 + } +} + +#[derive(Debug)] +pub struct StaticTimeline { + time: Timecode, + sequences: HashMap>, + children: Vec>, + max_sequences: usize, + sequence_name_map: HashMap, +} + +impl StaticTimeline { + pub fn new(fr: Framerate) -> Self { + Self { + time: tcode_hmsf_framerate!(00:00:00:00, fr), + sequences: HashMap::new(), + children: Vec::new(), + max_sequences: 0, + sequence_name_map: HashMap::new(), + } + } + + #[inline] + pub fn framerate(&self) -> &Framerate { + self.time.framerate() + } + + /// # Panics + /// + /// TODO! + pub fn new_sequence(&mut self, name: &str) -> Option<&mut Sequence> { + self.max_sequences += 1; + + self.sequences + .insert(self.max_sequences, Sequence::::new()); + self.sequence_name_map + .insert(name.to_string(), self.max_sequences); + + self.sequences.get_mut(&self.max_sequences) + } + + #[inline] + pub fn get_sequence_pointer(&self, name: &str) -> Option<&usize> { + self.sequence_name_map.get(name) + } + + /// # Panics + /// + /// TODO! + #[inline] + pub fn get_sequence_with_pointer(&mut self, pointer: usize) -> Option<&mut Sequence> { + self.sequences.get_mut(&pointer) + } + + /// # Panics + /// + /// TODO! + pub fn get_sequence_with_name(&mut self, name: &str) -> Option<&mut Sequence> { + let ptr = self.get_sequence_pointer(name).unwrap(); + + self.get_sequence_with_pointer(*ptr) + } + + pub fn add_child(&mut self, child: StaticTimeline) { + self.children.push(child); + } + + pub fn get_child_mut(&mut self, id: usize) -> Option<&mut StaticTimeline> { + self.children.get_mut(id) + } + + pub fn children(&self) -> &Vec> { + &self.children + } + + pub fn time(&self) -> &Timecode { + &self.time + } + + pub fn add_by_duration(&mut self, d: Duration) { + self.time.add_by_duration(d); + } + + pub fn sub_by_duration(&mut self, d: Duration) { + self.time.sub_by_duration(d); + } + + pub fn set_by_duration(&mut self, d: Duration) { + self.time.set_by_duration(d); + } + + pub fn add_by_timestamp(&mut self, t: Timecode) { + self.time.add_by_timestamp(t); + } + + pub fn sub_by_timestamp(&mut self, t: Timecode) { + self.time.sub_by_timestamp(t); + } + + pub fn set_by_timestamp(&mut self, t: Timecode) { + self.time.set_by_timestamp(t); + } + + /// # Panics + /// + /// TODO! + #[inline] + pub fn tween_by_name(&mut self, sequence_name: &str) -> T { + let time = self.time.clone(); + let sequence = self.get_sequence_with_name(sequence_name).unwrap(); + sequence.tween(&time) + } + + /// # Panics + /// + /// TODO! + #[inline] + pub fn tween_by_pointer(&mut self, sequence_ptr: usize) -> T { + let time = self.time.clone(); + let sequence = self.get_sequence_with_pointer(sequence_ptr).unwrap(); + sequence.tween(&time) + } +} + +#[derive(Debug)] +pub struct Timeline { + time: Timecode, + sequences: AnyMap, + children: Vec, + max_sequences: usize, + sequence_name_map: HashMap, +} + +impl Timeline { + pub fn new(fr: Framerate) -> Self { + Self { + time: tcode_hmsf_framerate!(00:00:00:00, fr), + sequences: AnyMap::new(), + children: Vec::new(), + max_sequences: 0, + sequence_name_map: HashMap::new(), + } + } + + #[inline] + pub fn framerate(&self) -> &Framerate { + self.time.framerate() + } + + /// # Panics + /// + /// TODO! + pub fn new_sequence(&mut self, name: &str) -> Option<&mut Sequence> { + self.max_sequences += 1; + + if self + .sequences + .get::>>() + .is_none() + { + self.sequences.insert(HashMap::>::new()); + } + + let seq_list = self + .sequences + .get_mut::>>() + .unwrap(); + + seq_list.insert(self.max_sequences, Sequence::::new()); + + self.sequence_name_map + .insert(name.to_string(), self.max_sequences); + + seq_list.get_mut(&self.max_sequences) + } + + #[inline] + pub fn get_sequence_pointer(&self, name: &str) -> Option<&usize> { + self.sequence_name_map.get(name) + } + + /// # Panics + /// + /// TODO! + pub fn get_sequence_with_pointer( + &mut self, + pointer: usize, + ) -> Option<&mut Sequence> { + let seq_list = self + .sequences + .get_mut::>>() + .unwrap(); + + seq_list.get_mut(&pointer) + } + + /// # Panics + /// + /// TODO! + pub fn get_sequence_with_name( + &mut self, + name: &str, + ) -> Option<&mut Sequence> { + let ptr = self.get_sequence_pointer(name).unwrap(); + + self.get_sequence_with_pointer(*ptr) + } + + pub fn add_child(&mut self, child: Timeline) { + self.children.push(child); + } + + pub fn get_child_mut(&mut self, id: usize) -> Option<&mut Timeline> { + self.children.get_mut(id) + } + + pub fn children(&self) -> &Vec { + &self.children + } + + pub fn time(&self) -> &Timecode { + &self.time + } + + pub fn add_by_duration(&mut self, d: Duration) { + self.time.add_by_duration(d); + } + + pub fn sub_by_duration(&mut self, d: Duration) { + self.time.sub_by_duration(d); + } + + pub fn set_by_duration(&mut self, d: Duration) { + self.time.set_by_duration(d); + } + + pub fn add_by_timestamp(&mut self, t: Timecode) { + self.time.add_by_timestamp(t); + } + + pub fn sub_by_timestamp(&mut self, t: Timecode) { + self.time.sub_by_timestamp(t); + } + + pub fn set_by_timestamp(&mut self, t: Timecode) { + self.time.set_by_timestamp(t); + } + + /// # Panics + /// + /// TODO! + #[inline] + pub fn tween_by_name(&mut self, sequence_name: &str) -> T { + let time = self.time.clone(); + let sequence = self.get_sequence_with_name(sequence_name).unwrap(); + sequence.tween(&time) + } + + /// # Panics + /// + /// TODO! + #[inline] + pub fn tween_by_pointer(&mut self, sequence_ptr: usize) -> T { + let time = self.time.clone(); + let sequence = self.get_sequence_with_pointer(sequence_ptr).unwrap(); + sequence.tween(&time) + } +} + +#[derive(Debug)] +pub struct Sequence { + engine: AnimationEngine, + tree: BTreeMap>, + last_time: Timecode, +} + +impl Default for Sequence { + fn default() -> Self { + Self::new() + } +} + +impl Sequence { + pub fn new() -> Self { + Self { + engine: AnimationEngine::default(), + tree: BTreeMap::new(), + last_time: tcode_hmsf!(00:00:00:00), + } + } + + /// # Panics + /// + /// TODO! + pub fn tween(&mut self, time: &Timecode) -> T { + // TODO: Make it so it returns 'T::default' instead of panicking. + if !self.engine.is_running() && !self.engine.is_sequence_ended() { + let current_keyframe_binding = + self.get_keyframes_between(&self.last_time, time, time.framerate()); + let current_keyframe = current_keyframe_binding.last().unwrap(); + + let last_keyframe_wrapped = + self.find_first_keyframe_after_timestamp(¤t_keyframe.0, time.framerate()); + + match last_keyframe_wrapped { + Some(last_keyframe) => { + self.engine.set_new_animation( + current_keyframe.0.clone(), + last_keyframe.0.clone(), + current_keyframe.1.clone(), + last_keyframe.1.clone(), + ); + } + None => { + self.engine.set_new_end(current_keyframe.1.clone()); + } + } + + self.last_time.set_by_timestamp(time.clone()); + self.last_time.set_framerate(*time.framerate()); + } + + self.engine.tween(time) + } + + // Create and get second + + #[inline] + pub fn create_second_with_isize(&mut self, second: &isize) -> Option<&mut SecondLeaf> { + self.tree.insert(*second, SecondLeaf::::new()); + self.get_second_with_isize(second) + } + + #[inline] + pub fn create_second_with_timestamp(&mut self, time: &Timecode) -> Option<&mut SecondLeaf> { + let hours_to_sec = time.hours() * 3600; + let minutes_to_sec = time.minutes() * 60; + self.create_second_with_isize(&(hours_to_sec + minutes_to_sec + time.seconds())) + } + + #[inline] + pub fn get_second_with_isize(&mut self, second: &isize) -> Option<&mut SecondLeaf> { + self.tree.get_mut(second) + } + + #[inline] + pub fn get_second_with_timestamp(&mut self, time: &Timecode) -> Option<&mut SecondLeaf> { + let hours_to_sec = time.hours() * 3600; + let minutes_to_sec = time.minutes() * 60; + self.get_second_with_isize(&(hours_to_sec + minutes_to_sec + time.seconds())) + } + + #[inline] + pub fn get_or_create_second_with_isize( + &mut self, + second: &isize, + ) -> Option<&mut SecondLeaf> { + if self.get_second_with_isize(second).is_none() { + return self.create_second_with_isize(second); + } + + self.get_second_with_isize(second) + } + + #[inline] + pub fn get_or_create_second_with_timestamp( + &mut self, + time: &Timecode, + ) -> Option<&mut SecondLeaf> { + let hours_to_sec = time.hours() * 3600; + let minutes_to_sec = time.minutes() * 60; + self.get_or_create_second_with_isize(&(hours_to_sec + minutes_to_sec + time.seconds())) + } + + /// # Panics + /// + /// TODO! + pub fn add_keyframe_at_timestamp( + &mut self, + key: Keyframe, + time: &Timecode, + ) -> Option<&mut Keyframe> { + // TODO: Make it so it returns 'None' instead of panicking. + let second: &mut SecondLeaf = self.get_or_create_second_with_timestamp(time).unwrap(); + let frame: &mut FrameLeaf = second.get_or_create_frame_with_timestamp(time).unwrap(); + + frame.add_keyframe_at_timestamp(time, key) + } + + /// # Panics + /// + /// TODO! + pub fn get_keyframe_at_timestamp(&mut self, time: &Timecode) -> Option<&mut Keyframe> { + // TODO: Make it so it returns 'None' instead of panicking. + let second: &mut SecondLeaf = self.get_second_with_timestamp(time).unwrap(); + let frame: &mut FrameLeaf = second.get_frame_with_timestamp(time).unwrap(); + + frame.get_keyframe_at_timestamp(time) + } + + /// # Panics + /// + /// TODO! + pub fn add_keyframes_at_timestamp(&mut self, keyframes: Vec<(Keyframe, &Timecode)>) { + for k in keyframes { + // TODO: Make it so it returns 'None' instead of panicking. + self.add_keyframe_at_timestamp(k.0, k.1); + } + } + + /// # Panics + /// + /// TODO! + #[inline] + pub fn get_keyframes_between( + &self, + begin: &Timecode, + end: &Timecode, + fr: &Framerate, + ) -> Vec<(Timecode, Keyframe)> { + let mut final_vec: Vec<(Timecode, Keyframe)> = Vec::new(); + + let begin_secs = (begin.hours() * 3600) + (begin.minutes() * 60) + begin.seconds(); + let end_secs = (end.hours() * 3600) + (end.minutes() * 60) + end.seconds() + 1; + + for i in begin_secs..end_secs { + let sec_leaf_search: Option<&SecondLeaf> = self.tree.get(&i); + + if sec_leaf_search.is_none() { + continue; + } + + // TODO: Make it so it returns 'None' instead of panicking. + let sec_leaf: &SecondLeaf = sec_leaf_search.unwrap(); + + sec_leaf.get_keyframes_between(begin, end, i, fr, &mut final_vec); + } + + final_vec + } + + /// # Panics + /// + /// TODO! + #[inline] + pub fn find_first_keyframe_after_timestamp( + &self, + timestamp: &Timecode, + fr: &Framerate, + ) -> Option<(Timecode, Keyframe)> { + let begin_secs = + (timestamp.hours() * 3600) + (timestamp.minutes() * 60) + timestamp.seconds(); + let end_secs = *self.tree.iter().next_back().unwrap().0 + 1; + + for i in begin_secs..end_secs { + let sec_leaf_search: Option<&SecondLeaf> = self.tree.get(&i); + + if sec_leaf_search.is_none() { + continue; + } + + // TODO: Make it so it returns 'None' instead of panicking. + let sec_leaf: &SecondLeaf = sec_leaf_search.unwrap(); + + let keyframe = sec_leaf.find_first_keyframe_after_timestamp(timestamp, i, fr); + + if keyframe.is_none() { + continue; + } + + return keyframe; + } + + None + } +} + +#[derive(Debug)] +pub struct SecondLeaf { + frames: BTreeMap>, +} + +impl Default for SecondLeaf { + fn default() -> Self { + Self::new() + } +} + +impl SecondLeaf { + pub fn new() -> Self { + Self { + frames: BTreeMap::new(), + } + } + + // Create and get frame + + #[inline] + pub fn create_frame_with_isize(&mut self, frame: &isize) -> Option<&mut FrameLeaf> { + self.frames.insert(*frame, FrameLeaf::::new()); + self.get_frame_with_isize(frame) + } + + #[inline] + pub fn create_frame_with_timestamp(&mut self, time: &Timecode) -> Option<&mut FrameLeaf> { + self.create_frame_with_isize(time.frames()) + } + + #[inline] + pub fn get_frame_with_isize(&mut self, frame: &isize) -> Option<&mut FrameLeaf> { + self.frames.get_mut(frame) + } + + #[inline] + pub fn get_frame_with_timestamp(&mut self, time: &Timecode) -> Option<&mut FrameLeaf> { + self.get_frame_with_isize(time.frames()) + } + + #[inline] + pub fn get_or_create_frame_with_isize(&mut self, frame: &isize) -> Option<&mut FrameLeaf> { + if self.get_frame_with_isize(frame).is_none() { + return self.create_frame_with_isize(frame); + } + + self.get_frame_with_isize(frame) + } + + #[inline] + pub fn get_keyframes_between( + &self, + begin: &Timecode, + end: &Timecode, + current_second: isize, + fr: &Framerate, + final_vec: &mut Vec<(Timecode, Keyframe)>, + ) { + let begin_frames: isize = if current_second == *begin.seconds() { + *begin.frames() + } else { + 0 + }; + + let end_frames: isize = if current_second == *end.seconds() { + *end.frames() + } else { + fr.as_f64() as isize + }; + + for fra_leaf in &self.frames { + if *fra_leaf.0 < begin_frames - 1 { + continue; + } + + if *fra_leaf.0 > end_frames { + break; + } + + fra_leaf.1.get_keyframes_between( + begin, + end, + current_second, + *fra_leaf.0, + fr, + final_vec, + ); + } + } + + #[inline] + pub fn find_first_keyframe_after_timestamp( + &self, + timestamp: &Timecode, + current_second: isize, + fr: &Framerate, + ) -> Option<(Timecode, Keyframe)> { + let begin_frames: isize = if current_second == *timestamp.seconds() { + *timestamp.frames() + } else { + 0 + }; + + for fra_leaf in &self.frames { + if *fra_leaf.0 < begin_frames { + continue; + } + + let keyframe = fra_leaf.1.find_first_keyframe_after_timestamp( + timestamp, + current_second, + *fra_leaf.0, + fr, + ); + + if keyframe.is_none() { + continue; + } + + return keyframe; + } + + None + } + + #[inline] + pub fn get_or_create_frame_with_timestamp( + &mut self, + time: &Timecode, + ) -> Option<&mut FrameLeaf> { + self.get_or_create_frame_with_isize(time.frames()) + } +} + +#[derive(Debug, Clone)] +pub struct FrameLeaf { + nanos: BTreeMap>, +} + +impl Default for FrameLeaf { + fn default() -> Self { + Self::new() + } +} + +impl FrameLeaf { + pub fn new() -> Self { + Self { + nanos: BTreeMap::new(), + } + } + + #[inline] + pub fn add_keyframe_at_isize( + &mut self, + nanos: &isize, + key: Keyframe, + ) -> Option<&mut Keyframe> { + self.nanos.insert(*nanos, key); + self.get_keyframe_at_isize(nanos) + } + + #[inline] + pub fn add_keyframe_at_timestamp( + &mut self, + time: &Timecode, + key: Keyframe, + ) -> Option<&mut Keyframe> { + self.add_keyframe_at_isize(time.nanoframes(), key) + } + + #[inline] + pub fn get_keyframe_at_isize(&mut self, frame: &isize) -> Option<&mut Keyframe> { + self.nanos.get_mut(frame) + } + + #[inline] + pub fn get_keyframe_at_timestamp(&mut self, time: &Timecode) -> Option<&mut Keyframe> { + self.get_keyframe_at_isize(time.nanoframes()) + } + + #[inline] + pub fn get_keyframes_between( + &self, + begin: &Timecode, + end: &Timecode, + current_second: isize, + current_frame: isize, + fr: &Framerate, + final_vec: &mut Vec<(Timecode, Keyframe)>, + ) { + let begin_nanos: isize = + if current_second == *begin.seconds() && current_frame == *begin.frames() { + *begin.nanoframes() + } else { + 0 + }; + + let end_nanos: isize = if current_second == *end.seconds() && current_frame == *end.frames() + { + *end.nanoframes() + } else { + 1_000_000_000 + }; + + for nano_leaf in &self.nanos { + if *nano_leaf.0 < begin_nanos - 1 { + continue; + } + + if *nano_leaf.0 > end_nanos { + break; + } + + let nanoframes = *nano_leaf.0; + + final_vec.push(( + tcode_full!(00:00:current_second:current_frame:nanoframes, *fr), + nano_leaf.1.clone(), + )); + } + } + + #[inline] + pub fn find_first_keyframe_after_timestamp( + &self, + timestamp: &Timecode, + current_second: isize, + current_frame: isize, + fr: &Framerate, + ) -> Option<(Timecode, Keyframe)> { + let begin_nanos: isize = if current_frame == *timestamp.frames() { + *timestamp.nanoframes() + } else { + 0 + }; + + for nano_leaf in &self.nanos { + if *nano_leaf.0 < begin_nanos { + continue; + } + + let nanoframes = *nano_leaf.0; + + let time = tcode_full!(00:00:current_second:current_frame:nanoframes, *fr); + + if !time.is_equals_to_hmsf(timestamp) { + return Some((time, nano_leaf.1.clone())); + } + } + + None + } +} + +#[derive(Debug, Default)] +pub struct AnimationEngine { + t_begin: Timecode, + t_end: Timecode, + k_begin: Keyframe, + k_end: Keyframe, + status: AnimationEngineStatus, +} + +#[derive(Debug, Clone, Default, PartialEq)] +pub enum AnimationEngineStatus { + #[default] + Empty, + Running, + Ended, + SequenceEnded, +} + +impl AnimationEngine { + pub fn tween(&mut self, current_time: &Timecode) -> T { + if self.status == AnimationEngineStatus::SequenceEnded { + return self.k_end.value.clone(); + } + + let time = current_time.get_lerp_time_between(&self.t_begin, &self.t_end); + + if time >= 1.0 { + self.status = AnimationEngineStatus::Ended; + } + + self.k_begin + .value + .tween(&self.k_end.value, time, &Easing::LERP) + } + + pub fn set_new_animation( + &mut self, + begin: Timecode, + end: Timecode, + k_begin: Keyframe, + k_end: Keyframe, + ) { + self.t_begin = begin; + self.t_end = end; + self.k_begin = k_begin; + self.k_end = k_end; + + self.status = AnimationEngineStatus::Running; + } + + pub fn set_new_end(&mut self, k: Keyframe) { + self.k_end = k; + self.status = AnimationEngineStatus::SequenceEnded; + } + + pub fn is_empty(&self) -> bool { + matches!(self.status, AnimationEngineStatus::Empty) + } + + pub fn is_running(&self) -> bool { + matches!(self.status, AnimationEngineStatus::Running) + } + + pub fn has_ended(&self) -> bool { + matches!(self.status, AnimationEngineStatus::Ended) + } + + pub fn is_sequence_ended(&self) -> bool { + matches!(self.status, AnimationEngineStatus::SequenceEnded) + } +} + +#[derive(Debug, Default, Clone)] +pub struct Keyframe { + pub value: T, +} diff --git a/src/value.rs b/src/value.rs index 329cdde..9c0e327 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,6 +1,7 @@ // Copyright 2024 the Interpoli Authors // SPDX-License-Identifier: Apache-2.0 OR MIT +use alloc::fmt::Debug; use alloc::vec::Vec; use peniko::{self, kurbo}; @@ -155,7 +156,8 @@ impl Animated { } /// Something that can be interpolated with an easing function. -pub trait Tween: Clone + Default { +pub trait Tween: Debug + Clone + Default { + #[must_use] fn tween(&self, other: &Self, t: f64, easing: &Easing) -> Self; } @@ -177,6 +179,13 @@ impl Tween for f64 { } } +impl Tween for f32 { + fn tween(&self, other: &Self, t: f64, _easing: &Easing) -> Self { + // Same TODO as f64 + keyframe::ease(keyframe::functions::Linear, *self, *other, t) + } +} + impl Tween for kurbo::Point { fn tween(&self, other: &Self, t: f64, easing: &Easing) -> Self { Self::new(