use std::time::Duration; use bevy::{ ecs::{ entity::{EntityMapper, MapEntities}, reflect::ReflectMapEntities, }, prelude::*, utils::HashSet, }; use bevy_tweening::{ lens::{TransformPositionLens, TransformRotationLens, TransformScaleLens}, Animator, EaseMethod, Sequence, Targetable, Tracks, Tween, TweenCompleted, }; use itertools::Itertools; use serde::{Deserialize, Serialize}; use space_editor::space_prefab::editor_registry::EditorRegistryExt; use crate::interaction::{MarkedForDeletion, Selectable}; 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::() .add_event::() .add_event::(); app.insert_resource(GlobalSeekTime { time: 0., playing: false, delta: None, }) .add_event::(); 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( First, ( 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; #[derive(Event, Clone, Copy, Debug)] pub enum TimelineInteractionEvent { Play, Pause, Seek(f32), } #[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, MapEntities)] pub struct Keyframe { target: Entity, 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 MapEntities for Keyframe { fn map_entities(&mut self, entity_mapper: &mut EntityMapper) { self.target = entity_mapper.get_or_reserve(self.target); } } impl Default for Keyframe { fn default() -> Self { Self { target: Entity::PLACEHOLDER, time: Default::default(), } } } #[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)] #[reflect(Component)] pub struct AnimationTarget; 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