Compare commits

...

8 Commits

Author SHA1 Message Date
Mark Moissette
f1f1ed9cfc
Merge 49dd0bc536 into 6c34ab8bd6 2024-03-30 22:44:11 +00:00
kaosat.dev
49dd0bc536 chore(): cargo fmt 2024-03-30 23:43:58 +01:00
kaosat.dev
2523691513 refactor(animation): cleanups , reorg & tweaks 2024-03-30 23:43:09 +01:00
kaosat.dev
df3e335114 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
2024-03-30 18:29:43 +01:00
Jan Hohenheim
6c34ab8bd6
feat(bevy_gltf_components): Improve global illumination to match Blender ambient lighting better(#174)
* Improves global illumination/ ambient by switching to an env map
2024-03-25 11:32:04 +01:00
Mark Moissette
bb4b07b7af
docs(bevy_gltf_blueprints): fixed missing link, minor cleanups (#172)
* the rest of fixes & cleanups was already done in a previous commit, closes #165
2024-03-21 23:07:57 +01:00
Mark Moissette
f438116ece
chore(bevy_gltf_blueprints): version bump (#171) 2024-03-21 22:16:44 +01:00
Jan Hohenheim
ada54450a5
fix(bevy_gltf_blueprints): fix occasional crash (#168)
* Fixes #156
* Fix post processing not affecting visibility of entities
2024-03-21 21:31:35 +01:00
12 changed files with 571 additions and 336 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gltf_blueprints"
version = "0.10.0"
version = "0.10.2"
authors = ["Mark 'kaosat-dev' Moissette"]
description = "Adds the ability to define Blueprints/Prefabs for Bevy inside gltf files and spawn them in Bevy."
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"

View File

@ -224,7 +224,7 @@ Typically , the order of systems should be
***bevy_gltf_components (GltfComponentsSet::Injection)*** => ***bevy_gltf_blueprints (GltfBlueprintsSet::Spawn, GltfBlueprintsSet::AfterSpawn)*** => ***replace_proxies***
see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/basic for how to set it up correctly
see an example [here](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/basic) for how to set it up correctly
@ -278,9 +278,9 @@ pub fn animation_change_on_proximity_foxes(
}
```
see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation for how to set it up correctly
see [here](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation) for how to set it up correctly
particularly from https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation/game/in_game.rs
particularly from [here](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation/src/game/in_game.rs)
## Materials
@ -301,7 +301,7 @@ material_library_folder: "materials".into() //defaults to "materials" the folder
```bevy_gltf_blueprints``` currently does NOT take care of loading those at runtime
see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/materials for how to set it up correctly
see an example [here](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/materials) for how to set it up correctly
Generating optimised blueprints and material libraries can be automated using the latests version of the [Blender plugin](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/gltf_auto_export)
@ -326,15 +326,15 @@ As it create custom properties that are writen in real **ron** file format inste
## Examples
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/basic
* [basic](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/basic)
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/basic_xpbd_physics
* [xbpd](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/basic_xpbd_physics)
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation
* [animation](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation)
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/materials
* [materials](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/materials)
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles
* [multiple_levels_multiple_blendfiles](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles)
## Compatible Bevy versions

View File

@ -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`
@ -36,12 +29,54 @@ pub struct InstanceAnimations {
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
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,
}
pub struct AnimationMarker{
pub frame:u32,
/// 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,
}

View File

@ -19,7 +19,11 @@ pub use copy_components::*;
use core::fmt;
use std::path::PathBuf;
use bevy::{prelude::*, render::primitives::Aabb, utils::HashMap};
use bevy::{
prelude::*,
render::{primitives::Aabb, view::VisibilitySystems},
utils::HashMap,
};
use bevy_gltf_components::{ComponentsFromGltfPlugin, GltfComponentsSet};
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
@ -119,17 +123,16 @@ impl Plugin for BlueprintsPlugin {
.register_type::<BlueprintName>()
.register_type::<MaterialInfo>()
.register_type::<SpawnHere>()
.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> >>>()
.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,
@ -174,10 +177,11 @@ impl Plugin for BlueprintsPlugin {
.in_set(GltfBlueprintsSet::Spawn),
)
.add_systems(
Update,
PostUpdate,
(spawned_blueprint_post_process, apply_deferred)
.chain()
.in_set(GltfBlueprintsSet::AfterSpawn),
.in_set(GltfBlueprintsSet::AfterSpawn)
.before(VisibilitySystems::CheckVisibility),
);
}
}

View File

@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
use bevy::{gltf::Gltf, prelude::*, utils::HashMap};
use crate::{BlueprintAnimations, BluePrintsConfig};
use crate::{BluePrintsConfig, BlueprintAnimations};
/// this is a flag component for our levels/game world
#[derive(Component)]
@ -281,9 +281,10 @@ pub(crate) fn spawn_from_blueprints(
},
Spawned,
OriginalChildren(original_children),
BlueprintAnimations { // these are animations specific to the inside of the blueprint
BlueprintAnimations {
// these are animations specific to the inside of the blueprint
named_animations: gltf.named_animations.clone(),
}
},
));
if add_to_world.is_some() {

View File

@ -85,7 +85,9 @@ pub(crate) fn spawned_blueprint_post_process(
// FIXME: stopgap solution: since we cannot use an AnimationPlayer at the root entity level
// and we cannot update animation clips so that the EntityPaths point to one level deeper,
// BUT we still want to have some marker/control at the root entity level, we add this
commands.entity(original).insert(BlueprintAnimationPlayerLink(added));
commands
.entity(original)
.insert(BlueprintAnimationPlayerLink(added));
}
}
}

View File

@ -1,5 +1,10 @@
use bevy::pbr::DirectionalLightShadowMap;
use bevy::prelude::*;
use bevy::render::render_asset::RenderAssetUsages;
use bevy::render::render_resource::{
Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor, TextureViewDimension,
};
use std::iter;
use crate::GltfComponentsSet;
@ -84,14 +89,66 @@ fn process_shadowmap(
}
fn process_background_shader(
background_shaders: Query<&BlenderBackgroundShader, Added<BlenderBackgroundShader>>,
background_shaders: Query<Ref<BlenderBackgroundShader>>,
cameras: Query<(Entity, Ref<Camera3d>)>,
mut images: ResMut<Assets<Image>>,
mut commands: Commands,
mut env_map_handle: Local<Option<Handle<Image>>>,
) {
for background_shader in background_shaders.iter() {
commands.insert_resource(AmbientLight {
color: background_shader.color,
// Just a guess, see <https://github.com/bevyengine/bevy/issues/12280>
brightness: background_shader.strength * 400.0,
let Ok(background_shader) = background_shaders.get_single() else {
return;
};
let env_map_handle = env_map_handle.get_or_insert_with(|| {
let size = Extent3d {
width: 1,
height: 6,
depth_or_array_layers: 1,
};
let dimension = TextureDimension::D2;
const SIDES_PER_CUBE: usize = 6;
let data: Vec<_> = iter::repeat(background_shader.color.as_rgba_u8())
.take(SIDES_PER_CUBE)
.flatten()
.collect();
let format = TextureFormat::Rgba8UnormSrgb;
let asset_usage = RenderAssetUsages::RENDER_WORLD;
let mut image = Image::new(size, dimension, data, format, asset_usage);
// Source: https://github.com/bevyengine/bevy/blob/85b488b73d6f6e75690962fba67a144d9beb6b88/examples/3d/skybox.rs#L152-L160
image.reinterpret_stacked_2d_as_array(image.height() / image.width());
image.texture_view_descriptor = Some(TextureViewDescriptor {
dimension: Some(TextureViewDimension::Cube),
..default()
});
images.add(image)
});
// Don't need the handle to be &mut
let env_map_handle = &*env_map_handle;
if background_shader.is_added() {
// We're using an environment map, so we don't need the ambient light
commands.remove_resource::<AmbientLight>();
}
let is_bg_outdated = background_shader.is_changed();
if is_bg_outdated {
let color = background_shader.color * background_shader.strength;
commands.insert_resource(ClearColor(color));
}
let camera_entities = cameras
.iter()
.filter_map(|(entity, cam)| (is_bg_outdated || cam.is_changed()).then_some(entity));
for camera_entity in camera_entities {
// See https://github.com/KhronosGroup/glTF-Blender-IO/blob/8573cc0dfb612091bfc1bcf6df55c18a44b9668a/addons/io_scene_gltf2/blender/com/gltf2_blender_conversion.py#L19
const PBR_WATTS_TO_LUMENS: f32 = 683.0;
commands.entity(camera_entity).insert(EnvironmentMapLight {
diffuse_map: env_map_handle.clone(),
specular_map: env_map_handle.clone(),
intensity: background_shader.strength * PBR_WATTS_TO_LUMENS,
});
}
}

View File

@ -110,6 +110,19 @@
"type": "array",
"typeInfo": "List"
},
"alloc::vec::Vec<bevy_gltf_blueprints::animation::AnimationInfo>": {
"isComponent": false,
"isResource": false,
"items": {
"type": {
"$ref": "#/$defs/bevy_gltf_blueprints::animation::AnimationInfo"
}
},
"short_name": "Vec<AnimationInfo>",
"title": "alloc::vec::Vec<bevy_gltf_blueprints::animation::AnimationInfo>",
"type": "array",
"typeInfo": "List"
},
"alloc::vec::Vec<bevy_render::color::Color>": {
"isComponent": false,
"isResource": false,
@ -2947,36 +2960,36 @@
"type": "object",
"typeInfo": "Struct"
},
"bevy_example::game::Marker1": {
"bevy_example::game::animation::Marker1": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {},
"required": [],
"short_name": "Marker1",
"title": "bevy_example::game::Marker1",
"title": "bevy_example::game::animation::Marker1",
"type": "object",
"typeInfo": "Struct"
},
"bevy_example::game::Marker2": {
"bevy_example::game::animation::Marker2": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {},
"required": [],
"short_name": "Marker2",
"title": "bevy_example::game::Marker2",
"title": "bevy_example::game::animation::Marker2",
"type": "object",
"typeInfo": "Struct"
},
"bevy_example::game::Marker3": {
"bevy_example::game::animation::Marker3": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {},
"required": [],
"short_name": "Marker3",
"title": "bevy_example::game::Marker3",
"title": "bevy_example::game::animation::Marker3",
"type": "object",
"typeInfo": "Struct"
},
@ -3549,25 +3562,90 @@
"type": "object",
"typeInfo": "Struct"
},
"bevy_gltf_blueprints::animation::Animated": {
"bevy_gltf_blueprints::animation::AnimationInfo": {
"additionalProperties": false,
"isComponent": false,
"isResource": false,
"properties": {
"frame_end": {
"type": {
"$ref": "#/$defs/f32"
}
},
"frame_end_override": {
"type": {
"$ref": "#/$defs/f32"
}
},
"frame_start": {
"type": {
"$ref": "#/$defs/f32"
}
},
"frame_start_override": {
"type": {
"$ref": "#/$defs/f32"
}
},
"frames_length": {
"type": {
"$ref": "#/$defs/f32"
}
},
"name": {
"type": {
"$ref": "#/$defs/alloc::string::String"
}
}
},
"required": [
"name",
"frame_start",
"frame_end",
"frames_length",
"frame_start_override",
"frame_end_override"
],
"short_name": "AnimationInfo",
"title": "bevy_gltf_blueprints::animation::AnimationInfo",
"type": "object",
"typeInfo": "Struct"
},
"bevy_gltf_blueprints::animation::AnimationInfos": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {
"animations": {
"type": {
"$ref": "#/$defs/alloc::vec::Vec<alloc::string::String>"
"$ref": "#/$defs/alloc::vec::Vec<bevy_gltf_blueprints::animation::AnimationInfo>"
}
}
},
"required": [
"animations"
],
"short_name": "Animated",
"title": "bevy_gltf_blueprints::animation::Animated",
"short_name": "AnimationInfos",
"title": "bevy_gltf_blueprints::animation::AnimationInfos",
"type": "object",
"typeInfo": "Struct"
},
"bevy_gltf_blueprints::animation::AnimationMarkers": {
"isComponent": true,
"isResource": false,
"items": false,
"prefixItems": [
{
"type": {
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
}
}
],
"short_name": "AnimationMarkers",
"title": "bevy_gltf_blueprints::animation::AnimationMarkers",
"type": "array",
"typeInfo": "TupleStruct"
},
"bevy_gltf_blueprints::animation::BlueprintAnimations": {
"additionalProperties": false,
"isComponent": true,
@ -10934,6 +11012,32 @@
"type": "object",
"typeInfo": "Map"
},
"bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
"additionalProperties": {
"type": {
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
}
},
"isComponent": false,
"isResource": false,
"short_name": "HashMap<String, HashMap<u32, Vec<String>, DefaultHashBuilder>, DefaultHashBuilder>",
"title": "bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>",
"type": "object",
"typeInfo": "Map"
},
"bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
"additionalProperties": {
"type": {
"$ref": "#/$defs/alloc::vec::Vec<alloc::string::String>"
}
},
"isComponent": false,
"isResource": false,
"short_name": "HashMap<u32, Vec<String>, DefaultHashBuilder>",
"title": "bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>",
"type": "object",
"typeInfo": "Map"
},
"bevy_utils::smallvec::SmallVec<[bevy_ecs::entity::Entity; 8]>": {
"isComponent": false,
"isResource": false,

View File

@ -0,0 +1,278 @@
use std::time::Duration;
use bevy_gltf_blueprints::{
AnimationInfos, AnimationMarkerReached, AnimationMarkerTrackers, AnimationMarkers,
BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintName, BlueprintsList,
GltfBlueprintsSet, InstanceAnimationPlayerLink, InstanceAnimations,
};
use bevy::{gltf::Gltf, prelude::*};
use bevy_gltf_worlflow_examples_common_rapier::{AppState, GameState};
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// flag component for testing
pub struct Marker1;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// flag component for testing
pub struct Marker2;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// flag component for testing
pub struct Marker3;
#[derive(Resource)]
pub struct AnimTest(Handle<Gltf>);
pub fn setup_main_scene_animations(asset_server: Res<AssetServer>, mut commands: Commands) {
commands.insert_resource(AnimTest(asset_server.load("models/World.glb")));
}
pub fn animations(
added_animation_players: Query<(Entity, &Name, &AnimationPlayer)>,
added_animation_infos: Query<(Entity, &Name, &AnimationInfos), (Added<AnimationInfos>)>,
animtest: Res<AnimTest>,
mut commands: Commands,
assets_gltf: Res<Assets<Gltf>>,
parents: Query<&Parent>,
) {
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 mut matching_data = true;
for animation_info in &animation_infos.animations {
if !gltf.named_animations.contains_key(&animation_info.name) {
matching_data = false;
break;
}
}
if matching_data {
println!(
"inserting Animations components into {} ({:?})",
name, entity
);
println!("Found match {:?}", gltf.named_animations);
commands.entity(entity).insert(InstanceAnimations {
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));
commands
.entity(entity)
.insert(InstanceAnimationPlayerLink(ancestor));
}
// info!("{:?} is an ancestor of {:?}", ancestor, player);
}
}
println!("");
}
}
pub fn play_animations(
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>>,
) {
if keycode.just_pressed(KeyCode::KeyM) {
for (link, animations) in animated_marker1.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint1_move";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
if keycode.just_pressed(KeyCode::KeyJ) {
for (link, animations) in animated_marker1.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint1_jump";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
if keycode.just_pressed(KeyCode::KeyA) {
for (link, animations) in animated_marker2.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint1_move";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
if keycode.just_pressed(KeyCode::KeyB) {
for (link, animations) in animated_marker2.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint1_jump";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
// play instance animation
if keycode.just_pressed(KeyCode::KeyW) {
for (link, animations, _, _) in animated_marker3.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint8_move";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
// play blueprint animation
if keycode.just_pressed(KeyCode::KeyX) {
for (_, _, link, animations) in animated_marker3.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Walk";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
}
pub fn trigger_event_based_on_animation_marker(
animation_infos: Query<(
Entity,
&AnimationMarkers,
&InstanceAnimationPlayerLink,
&InstanceAnimations,
&AnimationInfos,
)>,
animation_players: Query<&AnimationPlayer>,
animation_clips: Res<Assets<AnimationClip>>,
mut animation_marker_events: EventWriter<AnimationMarkerReached>,
) {
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_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[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(),
});
}
}
}
}
}
}
pub fn react_to_animation_markers(
mut animation_marker_events: EventReader<AnimationMarkerReached>,
) {
for event in animation_marker_events.read() {
println!("animation marker event {:?}", event)
}
}

View File

@ -1,13 +1,19 @@
pub mod animation;
pub mod in_game;
use std::{
collections::HashMap, fs, time::Duration
};
use bevy_gltf_blueprints::{Animated, AnimationMarkers, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintName, BlueprintsList, GltfBlueprintsSet, InstanceAnimationPlayerLink, InstanceAnimations};
pub use animation::*;
pub use in_game::*;
use std::{collections::HashMap, fs, time::Duration};
use bevy_gltf_blueprints::{
AnimationInfos, AnimationMarkerReached, AnimationMarkerTrackers, AnimationMarkers,
BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintName, BlueprintsList,
GltfBlueprintsSet, InstanceAnimationPlayerLink, InstanceAnimations,
};
use bevy::{
ecs::query, gltf::Gltf, prelude::*, render::view::screenshot::ScreenshotManager, time::common_conditions::on_timer, window::PrimaryWindow
ecs::query, gltf::Gltf, prelude::*, render::view::screenshot::ScreenshotManager,
time::common_conditions::on_timer, window::PrimaryWindow,
};
use bevy_gltf_worlflow_examples_common_rapier::{AppState, GameState};
@ -18,7 +24,6 @@ fn start_game(mut next_app_state: ResMut<NextState<AppState>>) {
next_app_state.set(AppState::AppLoading);
}
// if the export from Blender worked correctly, we should have animations (simplified here by using AnimationPlayerLink)
// if the export from Blender worked correctly, we should have an Entity called "Blueprint4_nested" that has a child called "Blueprint3" that has a "BlueprintName" component with value Blueprint3
// if the export from Blender worked correctly, we should have a blueprints_list
@ -33,7 +38,7 @@ fn validate_export(
empties_candidates: Query<(Entity, &Name, &GlobalTransform)>,
blueprints_list: Query<(Entity, &BlueprintsList)>,
root: Query<(Entity, &Name, &Children), (Without<Parent>, With<Children>)>
root: Query<(Entity, &Name, &Children), (Without<Parent>, With<Children>)>,
) {
let animations_found = !animation_player_links.is_empty();
@ -79,22 +84,23 @@ fn validate_export(
let mut tree: HashMap<String, Vec<String>> = HashMap::new();
for child in children.iter_descendants(root.0) {
let child_name:String = names.get(child).map_or(String::from("no_name"), |e| e.to_string() ); //|e| e.to_string(), || "no_name".to_string());
let child_name: String = names
.get(child)
.map_or(String::from("no_name"), |e| e.to_string()); //|e| e.to_string(), || "no_name".to_string());
//println!(" child {}", child_name);
let parent = parents.get(child).unwrap();
let parent_name:String = names.get(parent.get()).map_or(String::from("no_name"), |e| e.to_string() ); //|e| e.to_string(), || "no_name".to_string());
tree.entry(parent_name).or_default().push(child_name.clone());
let parent_name: String = names
.get(parent.get())
.map_or(String::from("no_name"), |e| e.to_string()); //|e| e.to_string(), || "no_name".to_string());
tree.entry(parent_name)
.or_default()
.push(child_name.clone());
}
let hierarchy = to_json_string(&tree);
fs::write(
"bevy_hierarchy.json",
hierarchy
)
.expect("unable to write hierarchy file")
fs::write("bevy_hierarchy.json", hierarchy).expect("unable to write hierarchy file")
}
fs::write(
"bevy_diagnostics.json",
format!(
@ -118,244 +124,6 @@ fn exit_game(mut app_exit_events: ResMut<Events<bevy::app::AppExit>>) {
app_exit_events.send(bevy::app::AppExit);
}
#[derive(Resource)]
struct MainAnimations(Vec<Handle<AnimationClip>>);
#[derive(Resource)]
struct AnimTest(Handle<Gltf>);
fn setup_main_scene_animations(
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
/*commands.insert_resource(MainAnimations(vec![
asset_server.load("models/World.glb#Blueprint1_jump"),
asset_server.load("models/World.glb#Blueprint1_move"),
// asset_server.load("models/library/Blueprint6_animated.glb#Run"),
]));*/
commands.insert_resource(AnimTest(asset_server.load("models/World.glb")));
}
fn animations(
added_animation_players:Query<(Entity, &Name, &AnimationPlayer)>,
addded_animateds:Query<(Entity, &Name, &Animated),(Added<Animated>)>,
animtest: Res<AnimTest>,
mut commands: Commands,
assets_gltf: Res<Assets<Gltf>>,
parents: Query<&Parent>,
names: Query<&Name>,
) {
for (entity, name, animated) in addded_animateds.iter() {
// println!("animated stuf {:?} on entity {}", animated, 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){
matching_data = false;
break;
}
}
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));
commands.entity(entity).insert(InstanceAnimationPlayerLink(ancestor));
}
// info!("{:?} is an ancestor of {:?}", ancestor, player);
}
}
}
}
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>)>,
mut animation_players: Query<&mut AnimationPlayer>,
keycode: Res<ButtonInput<KeyCode>>,
) {
if keycode.just_pressed(KeyCode::KeyM) {
for (link, animations) in animated_marker1.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint1_move";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
if keycode.just_pressed(KeyCode::KeyJ) {
for (link, animations) in animated_marker1.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint1_jump";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
if keycode.just_pressed(KeyCode::KeyA) {
for (link, animations) in animated_marker2.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint1_move";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
if keycode.just_pressed(KeyCode::KeyB) {
for (link, animations) in animated_marker2.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint1_jump";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
// play instance animation
if keycode.just_pressed(KeyCode::KeyW) {
for (link, animations, _, _) in animated_marker3.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint8_move";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
// play blueprint animation
if keycode.just_pressed(KeyCode::KeyX) {
for (_, _, link, animations) in animated_marker3.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Walk";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
}
fn trigger_event_based_on_animation_marker(
bla: Query<(Entity, &AnimationMarkers, &InstanceAnimationPlayerLink, &InstanceAnimations)>,
animation_players: Query<&AnimationPlayer>,
animation_clips: Res<Assets<AnimationClip>>
) {
for (entity, markers, link, animations) in bla.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(){
// println!("Entity {:?} markers {:?}", entity, markers);
// println!("Player {:?} {}", animation_player.elapsed(), animation_player.completions());
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 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);
}
}
}
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// flag component for testing
pub struct Marker1;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// flag component for testing
pub struct Marker2;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// flag component for testing
pub struct Marker3;
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
@ -374,6 +142,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,

View File

@ -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