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}