blenvy/blueprints/
animation.rs

1use bevy::prelude::*;
2use bevy::utils::HashMap;
3
4#[derive(Component, Reflect, Default, Debug)]
5#[reflect(Component)]
6/// storage for animations for a given entity's BLUEPRINT (ie for example a characters animations)
7pub struct BlueprintAnimations {
8    pub named_animations: HashMap<String, Handle<AnimationClip>>,
9    pub named_indices: HashMap<String, AnimationNodeIndex>,
10    pub graph: Handle<AnimationGraph>,
11}
12
13#[derive(Component, Debug)]
14/// Stop gap helper component : this is inserted into a "root" entity (an entity representing a whole gltf file)
15/// so that the root entity knows which of its children contains an actualy `AnimationPlayer` component
16/// this is for convenience, because currently , Bevy's gltf parsing inserts `AnimationPlayers` "one level down"
17/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
18pub struct BlueprintAnimationPlayerLink(pub Entity);
19
20#[derive(Component, Debug)]
21/// Same as the above but for `AnimationInfos` components which get added (on the Blender side) to the entities that actually have the animations
22/// which often is not the Blueprint or blueprint instance entity itself.
23pub struct BlueprintAnimationInfosLink(pub Entity);
24
25#[derive(Component, Reflect, Default, Debug)]
26#[reflect(Component)]
27/// storage for per instance / scene level animations for a given entity (hierarchy)
28pub struct InstanceAnimations {
29    pub named_animations: HashMap<String, Handle<AnimationClip>>,
30    pub named_indices: HashMap<String, AnimationNodeIndex>,
31    pub graph: Handle<AnimationGraph>,
32}
33
34#[derive(Component, Debug)]
35/// Stop gap helper component : this is inserted into a "root" entity (an entity representing a whole gltf file)
36/// so that the root entity knows which of its children contains an actualy `AnimationPlayer` component
37/// this is for convenience, because currently , Bevy's gltf parsing inserts `AnimationPlayers` "one level down"
38/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
39pub struct InstanceAnimationPlayerLink(pub Entity);
40
41#[derive(Component, Debug)]
42/// Same as the above but for scene's `AnimationInfos` components which get added (on the Blender side) to the entities that actually have the animations
43/// which often is not the Blueprint or blueprint instance entity itself.
44pub struct InstanceAnimationInfosLink(pub Entity);
45
46/// Stores Animation information: name, frame informations etc
47#[derive(Reflect, Default, Debug)]
48pub struct AnimationInfo {
49    pub name: String,
50    pub frame_start: f32,
51    pub frame_end: f32,
52    pub frames_length: f32,
53    pub frame_start_override: f32,
54    pub frame_end_override: f32,
55}
56
57/// Stores information about animations, to make things a bit easier api wise:
58/// these components are automatically inserted by the `blenvy` Blender add-on on entities that have animations
59#[derive(Component, Reflect, Default, Debug)]
60#[reflect(Component)]
61pub struct AnimationInfos {
62    pub animations: Vec<AnimationInfo>,
63}
64
65#[derive(Reflect, Default, Debug)]
66pub struct AnimationMarker {
67    // pub frame: u32,
68    pub name: String,
69    pub handled_for_cycle: bool,
70}
71
72/// Stores information about animation markers: practical for adding things like triggering events at specific keyframes etc
73/// it is essentiall a hashmap of `AnimationName` => `HashMap`<`FrameNumber`, Vec of marker names>
74#[derive(Component, Reflect, Default, Debug)]
75#[reflect(Component)]
76pub struct AnimationMarkers(pub HashMap<String, HashMap<u32, Vec<String>>>);
77
78/// Event that gets triggered once a specific marker inside an animation has been reached (frame based)
79/// Provides some usefull information about which entity , wich animation, wich frame & which marker got triggered
80#[derive(Event, Debug)]
81pub struct AnimationMarkerReached {
82    pub entity: Entity,
83    pub animation_name: String,
84    pub frame: u32,
85    pub marker_name: String,
86}
87
88/////////////////////
89
90/// triggers events when a given animation marker is reached for BLUEPRINT animations
91pub fn trigger_blueprint_animation_markers_events(
92    animation_data: Query<(
93        Entity,
94        &BlueprintAnimationPlayerLink,
95        &BlueprintAnimationInfosLink,
96        &BlueprintAnimations,
97    )>,
98    // FIXME: annoying hiearchy issue yet again: the Markers & AnimationInfos are stored INSIDE the blueprint, so we need to access them differently
99    animation_infos: Query<(&AnimationInfos, &AnimationMarkers)>,
100    animation_players: Query<&AnimationPlayer>,
101    mut animation_marker_events: EventWriter<AnimationMarkerReached>,
102
103    animation_clips: Res<Assets<AnimationClip>>,
104) {
105    for (entity, player_link, infos_link, animations) in animation_data.iter() {
106        for (animation_name, node_index) in animations.named_indices.iter() {
107            let animation_player = animation_players.get(player_link.0).unwrap();
108            let (animation_infos, animation_markers) = animation_infos.get(infos_link.0).unwrap();
109
110            if animation_player.animation_is_playing(*node_index) {
111                if let Some(animation) = animation_player.animation(*node_index) {
112                    // animation.speed()
113                    // animation.completions()
114                    if let Some(animation_clip_handle) =
115                        animations.named_animations.get(animation_name)
116                    {
117                        if let Some(animation_clip) = animation_clips.get(animation_clip_handle) {
118                            let animation_length_seconds = animation_clip.duration();
119                            let animation_length_frames =
120                                animation_infos // FIXME: horribly inneficient
121                                    .animations
122                                    .iter()
123                                    .find(|anim| &anim.name == animation_name)
124                                    .unwrap()
125                                    .frames_length;
126
127                            // TODO: we also need to take playback speed into account
128                            let time_in_animation = animation.elapsed()
129                                - (animation.completions() as f32) * animation_length_seconds;
130                            let frame_seconds = (animation_length_frames
131                                / animation_length_seconds)
132                                * time_in_animation;
133                            // println!("frame seconds {}", frame_seconds);
134                            let frame = frame_seconds.ceil() as u32; // FIXME , bad hack
135
136                            let matching_animation_marker = &animation_markers.0[animation_name];
137
138                            if matching_animation_marker.contains_key(&frame) {
139                                let matching_markers_per_frame =
140                                    matching_animation_marker.get(&frame).unwrap();
141                                println!(
142                                    "FOUND A MARKER {:?} at frame {}",
143                                    matching_markers_per_frame, frame
144                                );
145                                // FIXME: complete hack-ish solution , otherwise this can fire multiple times in a row, depending on animation length , speed , etc
146                                let diff = frame as f32 - frame_seconds;
147                                if diff < 0.1 {
148                                    for marker_name in matching_markers_per_frame {
149                                        animation_marker_events.send(AnimationMarkerReached {
150                                            entity,
151                                            animation_name: animation_name.clone(),
152                                            frame,
153                                            marker_name: marker_name.clone(),
154                                        });
155                                    }
156                                }
157                            }
158                        }
159                    }
160                }
161            }
162        }
163    }
164}
165
166/// triggers events when a given animation marker is reached for INSTANCE animations
167// TODO: implement this correctly
168pub fn trigger_instance_animation_markers_events(
169    animation_infos: Query<(
170        Entity,
171        &AnimationMarkers,
172        &InstanceAnimationPlayerLink,
173        &InstanceAnimations,
174        &AnimationInfos,
175    )>,
176    animation_players: Query<&AnimationPlayer>,
177    animation_clips: Res<Assets<AnimationClip>>,
178    __animation_graphs: Res<Assets<AnimationGraph>>,
179    mut _animation_marker_events: EventWriter<AnimationMarkerReached>,
180) {
181    for (__entity, __markers, player_link, animations, __animation_infos) in animation_infos.iter()
182    {
183        //let (animation_player, animation_transitions) = animation_players.get(player_link.0).unwrap();
184        //let foo = animation_transitions.get_main_animation().unwrap();
185
186        for (animation_name, node_index) in animations.named_indices.iter() {
187            let animation_player = animation_players.get(player_link.0).unwrap();
188            if animation_player.animation_is_playing(*node_index) {
189                if let Some(__animation) = animation_player.animation(*node_index) {
190                    if let Some(animation_clip_handle) =
191                        animations.named_animations.get(animation_name)
192                    {
193                        if let Some(__animation_clip) = animation_clips.get(animation_clip_handle) {
194                            println!("found the animation clip");
195                        }
196                    }
197                }
198            }
199        }
200
201        /*let animation_clip = animation_clips.get(animation_player.animation_clip());
202        // animation_player.play(animation)
203
204        if animation_clip.is_some() {
205            // println!("Entity {:?} markers {:?}", entity, markers);
206            // println!("Player {:?} {}", animation_player.elapsed(), animation_player.completions());
207            // FIMXE: yikes ! very inneficient ! perhaps add boilerplate to the "start playing animation" code so we know what is playing
208            let animation_name = animations.named_animations.iter().find_map(|(key, value)| {
209                if value == animation_player.animation_clip() {
210                    Some(key)
211                } else {
212                    None
213                }
214            });
215            if animation_name.is_some() {
216                let animation_name = animation_name.unwrap();
217
218                let animation_length_seconds = animation_clip.unwrap().duration();
219                let animation_length_frames = animation_infos
220                    .animations
221                    .iter()
222                    .find(|anim| &anim.name == animation_name)
223                    .unwrap()
224                    .frames_length;
225                // TODO: we also need to take playback speed into account
226                let time_in_animation = animation_player.elapsed()
227                    - (animation_player.completions() as f32) * animation_length_seconds;
228                let frame_seconds =
229                    (animation_length_frames / animation_length_seconds) * time_in_animation;
230                let frame = frame_seconds as u32;
231
232                let matching_animation_marker = &markers.0[animation_name];
233                if matching_animation_marker.contains_key(&frame) {
234                    let matching_markers_per_frame = matching_animation_marker.get(&frame).unwrap();
235
236                    // let timediff = animation_length_seconds - time_in_animation;
237                    // println!("timediff {}", timediff);
238                    // println!("FOUND A MARKER {:?} at frame {}", matching_markers_per_frame, frame);
239                    // emit an event AnimationMarkerReached(entity, animation_name, frame, marker_name)
240                    // FIXME: problem, this can fire multiple times in a row, depending on animation length , speed , etc
241                    for marker in matching_markers_per_frame {
242                        animation_marker_events.send(AnimationMarkerReached {
243                            entity,
244                            animation_name: animation_name.clone(),
245                            frame,
246                            marker_name: marker.clone(),
247                        });
248                    }
249                }
250            }
251        }*/
252    }
253}