feat(animation):
* renamed Animated component to AnimationInfos, fleshed it out with additional frame informations etc * restructured & cleaned up Bevy & Blender code in those areas a bit * various related minor tweaks
This commit is contained in:
parent
c44d82e7dc
commit
df3e335114
|
@ -3,7 +3,7 @@ use bevy::utils::HashMap;
|
|||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
/// storage for animations for a given entity (hierarchy), essentially a clone of gltf's `named_animations`
|
||||
/// storage for animations for a given entity's BLUEPRINT (ie for example a characters animations), essentially a clone of gltf's `named_animations`
|
||||
pub struct BlueprintAnimations {
|
||||
pub named_animations: HashMap<String, Handle<AnimationClip>>,
|
||||
}
|
||||
|
@ -15,13 +15,6 @@ pub struct BlueprintAnimations {
|
|||
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
|
||||
pub struct BlueprintAnimationPlayerLink(pub Entity);
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub struct Animated{
|
||||
pub animations: Vec<String>
|
||||
}
|
||||
|
||||
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
/// storage for animations for a given entity (hierarchy), essentially a clone of gltf's `named_animations`
|
||||
|
@ -37,11 +30,56 @@ pub struct InstanceAnimations {
|
|||
pub struct InstanceAnimationPlayerLink(pub Entity);
|
||||
|
||||
|
||||
|
||||
/// Stores Animation information: name, frame informations etc
|
||||
#[derive(Reflect, Default, Debug)]
|
||||
pub struct AnimationInfo {
|
||||
pub name: String,
|
||||
pub frame_start:f32,
|
||||
pub frame_end:f32,
|
||||
pub frames_length:f32,
|
||||
pub frame_start_override:f32,
|
||||
pub frame_end_override:f32
|
||||
}
|
||||
|
||||
/// Stores information about animations, to make things a bit easier api wise:
|
||||
/// these components are automatically inserted by gltf_auto_export on entities that have animations
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub struct AnimationInfos{
|
||||
pub animations: Vec<AnimationInfo>
|
||||
}
|
||||
|
||||
pub struct AnimationMarker{
|
||||
pub frame:u32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Stores information about animation markers: practical for adding things like triggering events at specific keyframes etc
|
||||
/// it is essentiall a hashmap of AnimationName => HashMap<FrameNumber, Vec of marker names>
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub struct AnimationMarkers(pub HashMap<String, HashMap<u32, Vec<String> >>);
|
||||
pub struct AnimationMarkers(pub HashMap<String, HashMap<u32, Vec<String> >>);
|
||||
|
||||
|
||||
// FIXME: ugh, ugly, there has to be a better way to do this ?
|
||||
#[derive(Component, Default, Debug)]
|
||||
pub struct AnimationMarkerTrackers(pub HashMap<String, HashMap<u32, Vec<AnimationMarkerTracker> >>);
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct AnimationMarkerTracker{
|
||||
// pub frame:u32,
|
||||
// pub name: String,
|
||||
// pub processed_for_cycle: bool,
|
||||
pub prev_frame: u32
|
||||
}
|
||||
|
||||
/// Event that gets triggered once a specific marker inside an animation has been reached (frame based)
|
||||
/// Provides some usefull information about which entity , wich animation, wich frame & which marker got triggered
|
||||
#[derive(Event, Debug)]
|
||||
pub struct AnimationMarkerReached{
|
||||
pub entity: Entity,
|
||||
pub animation_name: String,
|
||||
pub frame: u32,
|
||||
pub marker_name: String,
|
||||
}
|
||||
|
|
|
@ -122,14 +122,16 @@ impl Plugin for BlueprintsPlugin {
|
|||
|
||||
.register_type::<BlueprintAnimations>()
|
||||
.register_type::<InstanceAnimations>()
|
||||
.register_type::<Animated>()
|
||||
.register_type::<AnimationInfo>()
|
||||
.register_type::<AnimationInfos>()
|
||||
.register_type::<Vec<AnimationInfo>>()
|
||||
|
||||
.register_type::<AnimationMarkers>()
|
||||
.register_type::<HashMap<u32, Vec<String>>>()
|
||||
.register_type::<HashMap<String, HashMap<u32, Vec<String> >>>()
|
||||
|
||||
.add_event::<AnimationMarkerReached>()
|
||||
|
||||
.register_type::<BlueprintsList>()
|
||||
.register_type::<Vec<String>>()
|
||||
.register_type::<HashMap<String, Vec<String>>>()
|
||||
.insert_resource(BluePrintsConfig {
|
||||
format: self.format,
|
||||
|
|
Binary file not shown.
|
@ -3,7 +3,7 @@ use std::{
|
|||
collections::HashMap, fs, time::Duration
|
||||
};
|
||||
|
||||
use bevy_gltf_blueprints::{Animated, AnimationMarkers, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintName, BlueprintsList, GltfBlueprintsSet, InstanceAnimationPlayerLink, InstanceAnimations};
|
||||
use bevy_gltf_blueprints::{AnimationInfos, AnimationMarkerReached, AnimationMarkerTrackers, AnimationMarkers, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintName, BlueprintsList, GltfBlueprintsSet, InstanceAnimationPlayerLink, InstanceAnimations};
|
||||
pub use in_game::*;
|
||||
|
||||
use bevy::{
|
||||
|
@ -141,7 +141,7 @@ fn setup_main_scene_animations(
|
|||
|
||||
fn animations(
|
||||
added_animation_players:Query<(Entity, &Name, &AnimationPlayer)>,
|
||||
addded_animateds:Query<(Entity, &Name, &Animated),(Added<Animated>)>,
|
||||
added_animation_infos:Query<(Entity, &Name, &AnimationInfos),(Added<AnimationInfos>)>,
|
||||
|
||||
animtest: Res<AnimTest>,
|
||||
|
||||
|
@ -150,16 +150,13 @@ fn animations(
|
|||
|
||||
parents: Query<&Parent>,
|
||||
names: Query<&Name>,
|
||||
|
||||
) {
|
||||
for (entity, name, animated) in addded_animateds.iter() {
|
||||
// println!("animated stuf {:?} on entity {}", animated, name);
|
||||
for (entity, name, animation_infos) in added_animation_infos.iter() {
|
||||
//println!("animated stuf {:?} on entity {}", animation_infos, name);
|
||||
let gltf = assets_gltf.get(&animtest.0).unwrap();
|
||||
|
||||
let animations_list = animated;
|
||||
let mut matching_data = true;
|
||||
for animation_name in &animations_list.animations {
|
||||
if !gltf.named_animations.contains_key(animation_name){
|
||||
for animation_info in &animation_infos.animations {
|
||||
if !gltf.named_animations.contains_key(&animation_info.name){
|
||||
matching_data = false;
|
||||
break;
|
||||
}
|
||||
|
@ -167,34 +164,27 @@ fn animations(
|
|||
if matching_data {
|
||||
println!("inserting Animations components into {} ({:?})", name, entity);
|
||||
println!("Found match {:?}", gltf.named_animations);
|
||||
// commands.entity(entity).remove::<Animations>();
|
||||
// FIXME: for some reason this does NOT overwrite the component ??
|
||||
|
||||
commands.entity(entity).insert(
|
||||
InstanceAnimations {
|
||||
named_animations: gltf.named_animations.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
//animations.named_animations = gltf.named_animations.clone();
|
||||
|
||||
|
||||
for ancestor in parents.iter_ancestors(entity) {
|
||||
if added_animation_players.contains(ancestor) {
|
||||
println!("found match with animationPlayer !! {:?}",names.get(ancestor));
|
||||
// println!("found match with animationPlayer !! {:?}",names.get(ancestor));
|
||||
commands.entity(entity).insert(InstanceAnimationPlayerLink(ancestor));
|
||||
}
|
||||
// info!("{:?} is an ancestor of {:?}", ancestor, player);
|
||||
}
|
||||
}
|
||||
println!("");
|
||||
}
|
||||
}
|
||||
|
||||
fn play_animations(
|
||||
animated_marker1: Query<(&InstanceAnimationPlayerLink, &InstanceAnimations), (With<Animated>, With<Marker1>)>,
|
||||
animated_marker2: Query<(&InstanceAnimationPlayerLink, &InstanceAnimations), (With<Animated>, With<Marker2>)>,
|
||||
animated_marker3: Query<(&InstanceAnimationPlayerLink, &InstanceAnimations, &BlueprintAnimationPlayerLink, &BlueprintAnimations), (With<Animated>, With<Marker3>)>,
|
||||
animated_marker1: Query<(&InstanceAnimationPlayerLink, &InstanceAnimations), (With<AnimationInfos>, With<Marker1>)>,
|
||||
animated_marker2: Query<(&InstanceAnimationPlayerLink, &InstanceAnimations), (With<AnimationInfos>, With<Marker2>)>,
|
||||
animated_marker3: Query<(&InstanceAnimationPlayerLink, &InstanceAnimations, &BlueprintAnimationPlayerLink, &BlueprintAnimations), (With<AnimationInfos>, With<Marker3>)>,
|
||||
|
||||
mut animation_players: Query<&mut AnimationPlayer>,
|
||||
keycode: Res<ButtonInput<KeyCode>>,
|
||||
|
@ -309,35 +299,61 @@ fn play_animations(
|
|||
}
|
||||
|
||||
fn trigger_event_based_on_animation_marker(
|
||||
bla: Query<(Entity, &AnimationMarkers, &InstanceAnimationPlayerLink, &InstanceAnimations)>,
|
||||
animation_infos: Query<(Entity, &AnimationMarkers, &InstanceAnimationPlayerLink, &InstanceAnimations, &AnimationInfos)>,
|
||||
animation_players: Query<&AnimationPlayer>,
|
||||
animation_clips: Res<Assets<AnimationClip>>
|
||||
animation_clips: Res<Assets<AnimationClip>>,
|
||||
mut animation_marker_events: EventWriter<AnimationMarkerReached>
|
||||
) {
|
||||
for (entity, markers, link, animations) in bla.iter() {
|
||||
for (entity, markers, link, animations, animation_infos) in animation_infos.iter() {
|
||||
let animation_player = animation_players.get(link.0).unwrap();
|
||||
|
||||
let animation_clip = animation_clips.get(animation_player.animation_clip());
|
||||
|
||||
if animation_clip.is_some(){
|
||||
// if marker_trackers.0.contains_key(k)
|
||||
// marker_trackers.0
|
||||
// println!("Entity {:?} markers {:?}", entity, markers);
|
||||
// println!("Player {:?} {}", animation_player.elapsed(), animation_player.completions());
|
||||
|
||||
// FIMXE: yikes ! very inneficient ! perhaps add boilerplate to the "start playing animation" code so we know what is playing
|
||||
let animation_name = animations.named_animations.iter().find_map(|(key, value)| if value == animation_player.animation_clip(){ Some(key) }else { None});
|
||||
if animation_name.is_some() {
|
||||
let animation_name = animation_name.unwrap();
|
||||
|
||||
let animation_total_length = animation_clip.unwrap().duration();
|
||||
let animation_total_frames = 80; // FIXME just for testing
|
||||
// TODO: we also need to take playback speed into account
|
||||
let time_in_animation = animation_player.elapsed() - (animation_player.completions() as f32) * animation_total_length;//(animation_player.elapsed() / (animation_player.completions() as f32 + 1.0)) ;// / animation_total_length;
|
||||
let time_bla = (animation_total_frames as f32 / animation_total_length) * time_in_animation ;
|
||||
let frame = time_bla as u32;
|
||||
// println!("time_in_animation {} out of {}, completions {}, // frame {}",time_in_animation, animation_total_length, animation_player.completions(), frame);
|
||||
//animation_player.animation_clip()
|
||||
let animation_length_seconds = animation_clip.unwrap().duration();
|
||||
let animation_length_frames = animation_infos.animations.iter().find(|anim| &anim.name == animation_name).unwrap().frames_length;
|
||||
// TODO: we also need to take playback speed into account
|
||||
let time_in_animation = animation_player.elapsed() - (animation_player.completions() as f32) * animation_length_seconds;
|
||||
let frame_seconds = (animation_length_frames as f32 / animation_length_seconds) * time_in_animation ;
|
||||
let frame = frame_seconds as u32;
|
||||
|
||||
let matching_animation_marker = &markers.0[&"Blueprint1_jump".to_string()];
|
||||
if matching_animation_marker.contains_key(&frame) {
|
||||
let matching_markers_per_frame = matching_animation_marker.get(&frame).unwrap();
|
||||
println!("FOUND A MARKER {:?} at frame {}", matching_markers_per_frame, frame);
|
||||
|
||||
let matching_animation_marker = &markers.0[animation_name];
|
||||
if matching_animation_marker.contains_key(&frame) {
|
||||
let matching_markers_per_frame = matching_animation_marker.get(&frame).unwrap();
|
||||
// println!("FOUND A MARKER {:?} at frame {}", matching_markers_per_frame, frame);
|
||||
//emit an event , something like AnimationMarkerReached(entity, animation_name, frame, marker_name)
|
||||
// FIXME: problem, this can fire multiple times in a row, depending on animation length , speed , etc
|
||||
for marker_name in matching_markers_per_frame {
|
||||
animation_marker_events.send(AnimationMarkerReached {
|
||||
entity: entity,
|
||||
animation_name: animation_name.clone(),
|
||||
frame: frame,
|
||||
marker_name: marker_name.clone()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn react_to_animation_markers(
|
||||
mut animation_marker_events: EventReader<AnimationMarkerReached>
|
||||
)
|
||||
{
|
||||
for event in animation_marker_events.read() {
|
||||
println!("animation marker event {:?}", event)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,6 +390,8 @@ impl Plugin for GamePlugin {
|
|||
.after(GltfBlueprintsSet::AfterSpawn)
|
||||
)
|
||||
.add_systems(Update, play_animations)
|
||||
.add_systems(Update, react_to_animation_markers)
|
||||
|
||||
/* .add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once
|
||||
.add_systems(
|
||||
Update,
|
||||
|
|
|
@ -27,27 +27,17 @@ def remove_unwanted_custom_properties(object):
|
|||
# TODO: rename actions ?
|
||||
# reference https://github.com/KhronosGroup/glTF-Blender-IO/blob/main/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_action.py#L481
|
||||
def copy_animation_data(source, target):
|
||||
"""if source.data:
|
||||
data = source.data.copy()
|
||||
target.data = data"""
|
||||
if source.animation_data and source.animation_data:
|
||||
#print("copying animation data from", source.name, "to", target.name)
|
||||
print("I have animation data")
|
||||
ad = source.animation_data
|
||||
"""if ad.action:
|
||||
print(source.name,'uses',ad.action.name)"""
|
||||
|
||||
animations = []
|
||||
|
||||
blender_actions = []
|
||||
blender_tracks = {}
|
||||
|
||||
# TODO: this might need to be modified/ adapted to match the standard gltf exporter settings
|
||||
for track in ad.nla_tracks:
|
||||
#print("track", track.name, track.active)
|
||||
non_muted_strips = [strip for strip in track.strips if strip.action is not None and strip.mute is False]
|
||||
|
||||
for strip in non_muted_strips: #t.strips:
|
||||
print(" ", source.name,'uses',strip.action.name, "active", strip.active, "action", strip.action)
|
||||
# print(" ", source.name,'uses',strip.action.name, "active", strip.active, "action", strip.action)
|
||||
blender_actions.append(strip.action)
|
||||
blender_tracks[strip.action.name] = track.name
|
||||
|
||||
|
@ -57,31 +47,28 @@ def copy_animation_data(source, target):
|
|||
blender_actions.sort(key = lambda a: a.name.lower())
|
||||
|
||||
markers_per_animation = {}
|
||||
animations_infos = []
|
||||
|
||||
for action in blender_actions:
|
||||
animation_name = blender_tracks[action.name]
|
||||
animations.append(animation_name)
|
||||
|
||||
animations_infos.append(
|
||||
f'(name: "{animation_name}", frame_start: {action.frame_range[0]}, frame_end: {action.frame_range[1]}, frames_length: {action.frame_range[1] - action.frame_range[0]}, frame_start_override: {action.frame_start}, frame_end_override: {action.frame_end})'
|
||||
)
|
||||
markers_per_animation[animation_name] = {}
|
||||
|
||||
print("markers", action.pose_markers, "for", action.name)
|
||||
for marker in action.pose_markers:
|
||||
|
||||
if marker.frame not in markers_per_animation[animation_name]:
|
||||
markers_per_animation[animation_name][marker.frame] = []
|
||||
print(" marker", marker.name, marker.frame)
|
||||
|
||||
markers_per_animation[animation_name][marker.frame].append(marker.name)
|
||||
|
||||
print("animations", animations)
|
||||
|
||||
"""if target.animation_data == None:
|
||||
target.animation_data_create()
|
||||
target.animation_data.action = source.animation_data.action.copy()"""
|
||||
# alternative method, using the build in link animation operator
|
||||
# alternative method, using the built-in link animation operator
|
||||
with bpy.context.temp_override(active_object=source, selected_editable_objects=[target]):
|
||||
bpy.ops.object.make_links_data(type='ANIMATION')
|
||||
# we add an "animated" flag component
|
||||
target['Animated'] = f'(animations: {animations})'.replace("'", '"')
|
||||
# we add an "AnimationInfos" component
|
||||
target['AnimationInfos'] = f'(animations: {animations_infos})'.replace("'","")
|
||||
|
||||
markers_formated = '{'
|
||||
for animation in markers_per_animation.keys():
|
||||
|
@ -92,10 +79,7 @@ def copy_animation_data(source, target):
|
|||
markers_formated += f"{frame}:{markers}, ".replace("'", '"')
|
||||
markers_formated += '}, '
|
||||
markers_formated += '}'
|
||||
print("markers_formated", markers_formated)
|
||||
target["AnimationMarkers"] = f'( {markers_formated} )'
|
||||
#'({"animation_name": {5: ["Marker_1"]} })'
|
||||
#f'({json.dumps(markers_per_animation)})'
|
||||
|
||||
"""print("copying animation data for", source.name, target.animation_data)
|
||||
properties = [p.identifier for p in source.animation_data.bl_rna.properties if not p.is_readonly]
|
||||
|
@ -152,7 +136,7 @@ def duplicate_object(object, parent, combine_mode, destination_collection, libra
|
|||
copy.parent = parent_empty
|
||||
"""
|
||||
|
||||
print(nester, "copy", copy)
|
||||
# print(nester, "copy", copy)
|
||||
# do this both for empty replacements & normal copies
|
||||
if parent is not None:
|
||||
copy.parent = parent
|
||||
|
|
Loading…
Reference in New Issue