use std::{str::FromStr, time::Duration}; use bevy::{prelude::*, utils::hashbrown::HashMap}; use bevy_tweening::{ lens::{TransformPositionLens, TransformRotationLens, TransformScaleLens}, Animator, EaseMethod, Sequence, Targetable, Tracks, Tween, TweenCompleted, }; use itertools::Itertools; use serde::{Deserialize, Serialize}; use space_editor::{prelude::PrefabMarker, space_prefab::editor_registry::EditorRegistryExt}; use crate::{ headless::HeadlessSettings, interaction::{MarkedForDeletion, Selectable}, scene::SceneElement, }; pub struct TimelinePlugin; impl Plugin for TimelinePlugin { fn build(&self, app: &mut App) { app.register_type::() .editor_registry::() .register_type::() .editor_registry::() .register_type::() .editor_registry::() .register_type::() .editor_registry::() .register_type::() .editor_registry::() .register_type::() .register_type::() .add_event::() .add_event::(); app.insert_resource(GlobalSeekTime { time: 0., playing: false, delta: None, }) .add_event::(); app.add_systems(First, generate_transform_animation); app.add_systems(PreUpdate, (process_timeline_events, update_tick).chain()); app.add_systems( Update, ( process_keyframe_events, timeline_bound_component_animator_system::, ), ); // app.add_systems(PostUpdate, generate_transform_animation); #[cfg(feature = "wasm")] { app.add_systems( Last, ( wasm::update_global_seek_time, wasm::update_keyframe_interaction, ), ); app.add_systems( PostUpdate, ( wasm::notify_seek_time_changes.map(bevy::utils::error), wasm::notify_keyframe_changes.map(bevy::utils::error), wasm::notify_updated_animation_targets.map(bevy::utils::error), ), ); } } } const MIN_KEYFRAME_DISTANCE: f32 = 0.01; #[allow(dead_code)] #[derive(Event, Clone, Copy, Debug)] pub enum TimelineInteractionEvent { Play, Pause, Seek(f32), } #[allow(dead_code)] #[derive(Event, Clone, Copy, Debug)] pub enum KeyframeInteractionEvent { RecordKeyframe { target: Entity }, MoveKeyframeTime { target: Entity, time: f32 }, AddEntityTrack { target: Entity }, } #[derive(Resource, Clone, Copy, Debug, Default, Reflect, Serialize, Deserialize)] #[reflect(Resource)] pub struct GlobalSeekTime { pub time: f32, pub playing: bool, pub delta: Option, } #[derive(Clone, Debug, Copy, Default, Reflect, PartialEq, Serialize, Deserialize)] pub enum EaseType { #[default] Linear, CubicHermite, } #[derive(Component, Clone, Debug, Reflect, PartialEq)] #[reflect(Component)] pub struct TransformKeyframe { incoming_easing: EaseType, affect_translation: bool, affect_rotation: bool, affect_scale: bool, } impl Default for TransformKeyframe { fn default() -> Self { Self { incoming_easing: Default::default(), affect_translation: true, affect_rotation: true, affect_scale: true, } } } #[derive(Component, Clone, Debug, Reflect, PartialEq)] #[reflect(Component)] pub struct Keyframe { target: AnimationTarget, pub time: f32, } #[derive(Component, Clone, Debug, Default, Reflect, PartialEq, Eq, PartialOrd, Ord)] #[reflect(Component)] pub struct KeyframeOrder(u32); impl PartialOrd for Keyframe { fn partial_cmp(&self, other: &Self) -> Option { self.time.partial_cmp(&other.time) } } impl Default for Keyframe { fn default() -> Self { Self { time: Default::default(), target: AnimationTarget::Unknown, } } } #[derive(Component, Clone, Debug, Reflect, PartialEq)] #[reflect(Component)] pub struct TimedAnimationClipAsset { clip: Handle, duration: f32, speed: f32, } impl PartialOrd for TimedAnimationClipAsset { fn partial_cmp(&self, other: &Self) -> Option { match self.duration.partial_cmp(&other.duration) { Some(core::cmp::Ordering::Equal) => {} ord => return ord, } match self.speed.partial_cmp(&other.speed) { Some(core::cmp::Ordering::Equal) => {} ord => return ord, } None } } impl Default for TimedAnimationClipAsset { fn default() -> Self { Self { clip: Default::default(), duration: Default::default(), speed: 1.0, } } } #[derive( Component, Clone, Debug, Default, Reflect, PartialEq, Eq, Hash, Copy, Serialize, Deserialize, )] #[reflect(Component)] #[serde(try_from = "&str", into = "String")] pub enum AnimationTarget { #[default] Unknown, MainCamera, Uuid(uuid::Uuid), } impl AnimationTarget { pub fn new() -> Self { Self::Uuid(uuid::Uuid::new_v4()) } } impl ToString for AnimationTarget { fn to_string(&self) -> String { match self { Self::Unknown => "unknown".to_string(), Self::MainCamera => "MainCamera".to_string(), Self::Uuid(uuid) => uuid.to_string(), } } } impl From for String { fn from(val: AnimationTarget) -> Self { val.to_string() } } impl TryFrom<&str> for AnimationTarget { type Error = uuid::Error; fn try_from(value: &str) -> Result { value.parse() } } impl FromStr for AnimationTarget { type Err = uuid::Error; fn from_str(s: &str) -> Result { if s == "unknown" { Ok(Self::Unknown) } else if s == "MainCamera" { Ok(Self::MainCamera) } else { Ok(Self::Uuid(s.parse()?)) } } } fn process_timeline_events( mut e_timeline_event: EventReader, mut r_global_seek_time: ResMut, ) { for event in e_timeline_event.read() { match event { TimelineInteractionEvent::Play => r_global_seek_time.playing = true, TimelineInteractionEvent::Pause => { r_global_seek_time.playing = false; } TimelineInteractionEvent::Seek(time) => { r_global_seek_time.time = time.max(0.); } } r_global_seek_time.delta = None; } } fn update_tick( mut r_global_seek_time: ResMut, time: Res