From 30b052d4d223cbb65272b5334b7051f54b3891c3 Mon Sep 17 00:00:00 2001 From: "kaosat.dev" Date: Tue, 16 Jul 2024 23:39:09 +0200 Subject: [PATCH] feat(Blenvy:Bevy): * lots of doc updates * removed obsolete code * some cleanups * started adding & upgrading pieces of save_load into blenvy --- crates/blenvy/Cargo.toml | 4 +- crates/blenvy/README.md | 91 ++-- crates/blenvy/old/materials_old.rs | 190 --------- crates/blenvy/old/old.rs | 248 ----------- .../blenvy/old/spawn_from_blueprints copy.rs | 396 ------------------ crates/blenvy/old/spawn_post_process.rs | 165 -------- crates/blenvy/src/components/mod.rs | 2 +- crates/blenvy/src/lib.rs | 14 + 8 files changed, 75 insertions(+), 1035 deletions(-) delete mode 100644 crates/blenvy/old/materials_old.rs delete mode 100644 crates/blenvy/old/old.rs delete mode 100644 crates/blenvy/old/spawn_from_blueprints copy.rs delete mode 100644 crates/blenvy/old/spawn_post_process.rs diff --git a/crates/blenvy/Cargo.toml b/crates/blenvy/Cargo.toml index 3d4c758..5d2ff60 100644 --- a/crates/blenvy/Cargo.toml +++ b/crates/blenvy/Cargo.toml @@ -3,8 +3,8 @@ name = "blenvy" version = "0.1.0" authors = ["Mark 'kaosat-dev' Moissette"] description = "Allows you to define Bevy components direclty inside gltf files and instanciate the components on the Bevy side." -homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" -repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" +homepage = "https://github.com/kaosat-dev/Blenvy" +repository = "https://github.com/kaosat-dev/Blenvy" keywords = ["gamedev", "bevy", "assets", "gltf", "components"] categories = ["game-development"] edition = "2021" diff --git a/crates/blenvy/README.md b/crates/blenvy/README.md index 3fcde10..c5371c1 100644 --- a/crates/blenvy/README.md +++ b/crates/blenvy/README.md @@ -1,23 +1,39 @@ [![Crates.io](https://img.shields.io/crates/v/blenvy)](https://crates.io/crates/blenvy) [![Docs](https://img.shields.io/docsrs/blenvy)](https://docs.rs/blenvy/latest/blenvy/) -[![License](https://img.shields.io/crates/l/blenvy)](https://github.com/kaosat-dev/Blender_bevy_components_workflow/blob/main/crates/blenvy/License.md) +[![License](https://img.shields.io/crates/l/blenvy)](https://github.com/kaosat-dev/Blenvy/blob/main/crates/blenvy/License.md) [![Bevy tracking](https://img.shields.io/badge/Bevy%20tracking-released%20version-lightblue)](https://github.com/bevyengine/bevy/blob/main/docs/plugins_guidelines.md#main-branch-tracking) # blenvy -this crate adds the ability to define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy. +This crate allows you to +- define [Bevy](https://bevyengine.org/) components direclty inside gltf files and instanciate the components on the Bevy side. +- define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy. + * Allows you to create lightweight levels, where all assets are different gltf files and loaded after the main level is loaded + * Allows you to spawn different entities from gtlf files at runtime in a clean manner, including simplified animation support ! -* Allows you to create lightweight levels, where all assets are different gltf files and loaded after the main level is loaded -* Allows you to spawn different entities from gtlf files at runtime in a clean manner, including simplified animation support ! + A blueprint is a set of **overrideable** components + a hierarchy: ie -A blueprint is a set of **overrideable** components + a hierarchy: ie + * just a Gltf file with Gltf_extras specifying components + * a component called BlueprintInfo - * just a Gltf file with Gltf_extras specifying components - * a component called BlueprintInfo + Particularly useful when using [Blender](https://www.blender.org/) as an editor for the [Bevy](https://bevyengine.org/) game engine, combined with the Blender add-on that do a lot of the work for you + - [blenvy](https://github.com/kaosat-dev/Blenvy/tree/main/tools/blenvy) +- allows you to create a Json export of all your components/ registered types. +Its main use case is as a backbone for the [```blenvy``` Blender add-on](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/blenvy), that allows you to add & edit components directly in Blender, using the actual type definitions from Bevy +(and any of your custom types & components that you register in Bevy). +- adds the ability to easilly **save** and **load** your game worlds for [Bevy](https://bevyengine.org/) . -Particularly useful when using [Blender](https://www.blender.org/) as an editor for the [Bevy](https://bevyengine.org/) game engine, combined with the Blender add-on that do a lot of the work for you -- [blenvy](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/blenvy) +* leverages blueprints & seperation between + * **dynamic** entities : entities that can change during the lifetime of your app/game + * **static** entities : entities that do NOT change (typically, a part of your levels/ environements) +* and allows allow for : + * a simple save/load workflow thanks to the above + * ability to specify **which entities** to save or to exclude + * ability to specify **which components** to save or to exclude + * ability to specify **which resources** to save or to exclude + * small(er) save files (only a portion of the entities is saved) +Particularly useful when using [Blender](https://www.blender.org/) as an editor for the [Bevy](https://bevyengine.org/) game engine, combined with the [Blender plugin](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/gltf_auto_export) that does a lot of the work for you (including spliting generating seperate gltf files for your static vs dynamic assets) ## Usage @@ -40,12 +56,27 @@ fn main() { .add_plugins(DefaultPlugins) .add_plugins(BlenvyPlugin) + .add_systems(Startup, setup_game) + .add_systems(Update, spawn_blueprint_instance) .run(); } -// not shown here: any other setup that is not specific to blueprints -fn spawn_blueprint( +// this is how you setup & spawn a level from a blueprint +fn setup_game( + mut commands: Commands, +) { + + // here we spawn our game world/level, which is also a blueprint ! + commands.spawn(( + BlueprintInfo::from_path("levels/World.glb"), // all we need is a Blueprint info... + SpawnBlueprint, // and spawnblueprint to tell blenvy to spawn the blueprint now + HideUntilReady, // only reveal the level once it is ready + GameWorldTag, + )); +} + +fn spawn_blueprint_instance( mut commands: Commands, keycode: Res>, ){ @@ -152,7 +183,7 @@ any component you specify when spawning the Blueprint that is also specified **w for example ```rust no_run commands.spawn(( - BlueprintInfo("Health_Pickup".to_string()), + BlueprintInfo(path: "Health_Pickup.glb".into()), SpawnBlueprint, TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), HealthPowerUp(20)// if this is component is also present inside the "Health_Pickup" blueprint, that one will be replaced with this component during spawning @@ -168,7 +199,7 @@ There is also a ```BluePrintBundle``` for convenience , which just has ## Additional information -- When a blueprint is spawned, all its children entities (and nested children etc) also have an ```InBlueprint``` component that gets insert +- When a blueprint is spawned, an ```InBlueprint``` component is inserted into all its children entities (and nested children etc) - this crate also provides a special optional ```GameWorldTag``` component: this is useful when you want to keep all your spawned entities inside a root entity You can use it in your queries to add your entities as children of this "world" @@ -177,16 +208,10 @@ This way all your levels, your dynamic entities etc, are kept seperated from UI > Note: you should only have a SINGLE entity tagged with that component ! ```rust no_run - commands.spawn(( - SceneBundle { - scene: models - .get(game_assets.world.id()) - .expect("main level should have been loaded") - .scenes[0] - .clone(), - ..default() - }, - bevy::prelude::Name::from("world"), + commands.spawn(( + BlueprintInfo::from_path("levels/World.glb"), + SpawnBlueprint, + HideUntilReady, GameWorldTag, // here it is )); ``` @@ -204,7 +229,7 @@ Typically , the order of systems should be ***bevy_gltf_components (GltfComponentsSet::Injection)*** => ***blenvy (GltfBlueprintsSet::Spawn, GltfBlueprintsSet::AfterSpawn)*** => ***replace_proxies*** -see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/basic for how to set it up correctly +see https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/basic for how to set it up correctly @@ -258,9 +283,9 @@ pub fn animation_change_on_proximity_foxes( } ``` -see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/animation for how to set it up correctly +see https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/animation for how to set it up correctly -particularly from https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/animation/game/in_game.rs +particularly from https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/animation/game/in_game.rs ## Materials @@ -280,22 +305,22 @@ material_library: true // defaults to false, enable this to enable automatic in ```blenvy``` currently does NOT take care of loading those at runtime -see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/materials for how to set it up correctly +see https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/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) +Generating optimised blueprints and material libraries can be automated using the latests version of the [Blender plugin](https://github.com/kaosat-dev/Blenvy/tree/main/tools/gltf_auto_export) ## Examples -https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/basic +https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/basic -https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/basic_xpbd_physics +https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/basic_xpbd_physics -https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/animation +https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/animation -https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/materials +https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/materials -https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/multiple_levels_multiple_blendfiles +https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/multiple_levels_multiple_blendfiles ## Compatible Bevy versions diff --git a/crates/blenvy/old/materials_old.rs b/crates/blenvy/old/materials_old.rs deleted file mode 100644 index 8aa652d..0000000 --- a/crates/blenvy/old/materials_old.rs +++ /dev/null @@ -1,190 +0,0 @@ -use std::path::Path; - -use bevy::{ - asset::{AssetServer, Assets, Handle}, - ecs::{ - component::Component, - entity::Entity, - query::{Added, With}, - reflect::ReflectComponent, - system::{Commands, Query, Res, ResMut}, - }, - gltf::Gltf, - hierarchy::{Children, Parent}, - log::debug, - pbr::StandardMaterial, - reflect::Reflect, - render::mesh::Mesh, -}; - -use crate::{AssetLoadTracker, BlueprintAssetsLoadState, BlenvyConfig, BlueprintInstanceReady}; - -#[derive(Component, Reflect, Default, Debug)] -#[reflect(Component)] -/// struct containing the name & path of the material to apply -pub struct MaterialInfo { - pub name: String, - pub path: String, -} - -/// flag component -#[derive(Component)] -pub(crate) struct BlueprintMaterialAssetsLoaded; -/// flag component -#[derive(Component)] -pub(crate) struct BlueprintMaterialAssetsNotLoaded; - -/// system that injects / replaces materials from material library -pub(crate) fn materials_inject( - blenvy_config: ResMut, - material_infos: Query<(Entity, &MaterialInfo), Added>, - asset_server: Res, - mut commands: Commands, -) { - - - for (entity, material_info) in material_infos.iter() { - println!("Entity with material info {:?} {:?}", entity, material_info); - let material_full_path = format!("{}#{}", material_info.path, material_info.name); - if blenvy_config - .materials_cache - .contains_key(&material_full_path) - { - debug!("material is cached, retrieving"); - blenvy_config - .materials_cache - .get(&material_full_path) - .expect("we should have the material available"); - commands - .entity(entity) - .insert(BlueprintMaterialAssetsLoaded); - } else { - let material_file_handle = asset_server.load_untyped(&material_info.path.clone()); // : Handle - let material_file_id = material_file_handle.id(); - - let asset_infos: Vec = vec![AssetLoadTracker { - name: material_info.name.clone(), - path: material_info.path.clone(), - id: material_file_id, - loaded: false, - handle: material_file_handle.clone(), - }]; - - commands - .entity(entity) - .insert(BlueprintAssetsLoadState { - all_loaded: false, - asset_infos, - ..Default::default() - }) - .insert(BlueprintMaterialAssetsNotLoaded); - - } - } -} - -// TODO, merge with blueprints_check_assets_loading, make generic ? -pub(crate) fn check_for_material_loaded( - mut blueprint_assets_to_load: Query< - (Entity, &mut BlueprintAssetsLoadState), - With, - >, - asset_server: Res, - mut commands: Commands, -) { - for (entity, mut assets_to_load) in blueprint_assets_to_load.iter_mut() { - let mut all_loaded = true; - let mut loaded_amount = 0; - let total = assets_to_load.asset_infos.len(); - for tracker in assets_to_load.asset_infos.iter_mut() { - let asset_id = tracker.id; - let loaded = asset_server.is_loaded_with_dependencies(asset_id); - tracker.loaded = loaded; - if loaded { - loaded_amount += 1; - } else { - all_loaded = false; - } - } - let progress: f32 = loaded_amount as f32 / total as f32; - assets_to_load.progress = progress; - - if all_loaded { - assets_to_load.all_loaded = true; - commands - .entity(entity) - .insert(BlueprintMaterialAssetsLoaded) - .remove::(); - } - } -} - -/// system that injects / replaces materials from material library -pub(crate) fn materials_inject2( - mut blenvy_config: ResMut, - material_infos: Query< - (&MaterialInfo, &Children), - ( - Added, - With, - ), - >, - with_materials_and_meshes: Query< - (), - ( - With, - With>, - With>, - ), - >, - assets_gltf: Res>, - asset_server: Res, - - mut commands: Commands, -) { - for (material_info, children) in material_infos.iter() { - let material_full_path = format!("{}#{}", material_info.path, material_info.name); - let mut material_found: Option<&Handle> = None; - - if blenvy_config - .materials_cache - .contains_key(&material_full_path) - { - debug!("material is cached, retrieving"); - let material = blenvy_config - .materials_cache - .get(&material_full_path) - .expect("we should have the material available"); - material_found = Some(material); - } else { - let model_handle: Handle = asset_server.load(material_info.path.clone()); // FIXME: kinda weird now - let mat_gltf = assets_gltf - .get(model_handle.id()) - .expect("material should have been preloaded"); - if mat_gltf.named_materials.contains_key(&material_info.name as &str) { - let material = mat_gltf - .named_materials - .get(&material_info.name as &str) - .expect("this material should have been loaded"); - blenvy_config - .materials_cache - .insert(material_full_path, material.clone()); - material_found = Some(material); - } - } - - if let Some(material) = material_found { - for child in children.iter() { - if with_materials_and_meshes.contains(*child) { - debug!( - "injecting material {}, path: {:?}", - material_info.name, - material_info.path.clone() - ); - - commands.entity(*child).insert(material.clone()); - } - } - } - } -} diff --git a/crates/blenvy/old/old.rs b/crates/blenvy/old/old.rs deleted file mode 100644 index 0de0911..0000000 --- a/crates/blenvy/old/old.rs +++ /dev/null @@ -1,248 +0,0 @@ - - -/// helper component, for tracking loaded assets's loading state, id , handle etc -#[derive(Default, Debug)] -pub(crate) struct AssetLoadTracker { - #[allow(dead_code)] - pub name: String, - pub id: AssetId, - pub loaded: bool, - #[allow(dead_code)] - pub handle: Handle, -} - -/// helper component, for tracking loaded assets -#[derive(Component, Debug)] -pub(crate) struct AssetsToLoad { - pub all_loaded: bool, - pub asset_infos: Vec>, - pub progress: f32, -} -impl Default for AssetsToLoad { - fn default() -> Self { - Self { - all_loaded: Default::default(), - asset_infos: Default::default(), - progress: Default::default(), - } - } -} - -/// flag component, usually added when a blueprint is loaded -#[derive(Component)] -pub(crate) struct BlueprintAssetsLoaded; -/// flag component -#[derive(Component)] -pub(crate) struct BlueprintAssetsNotLoaded; - - - -/// spawning prepare function, -/// * also takes into account the already exisiting "override" components, ie "override components" > components from blueprint -pub(crate) fn prepare_blueprints( - spawn_placeholders: Query< - ( - Entity, - &BlueprintName, - Option<&Parent>, - Option<&Library>, - Option<&Name>, - Option<&BlueprintsList>, - ), - (Added, Added, Without), - >, - - mut commands: Commands, - asset_server: Res, - blenvy_config: Res, -) { - for (entity, blupeprint_name, original_parent, library_override, name, blueprints_list) in - spawn_placeholders.iter() - { - debug!( - "requesting to spawn {:?} for entity {:?}, id: {:?}, parent:{:?}", - blupeprint_name.0, name, entity, original_parent - ); - - // println!("main model path {:?}", model_path); - if blueprints_list.is_some() { - let blueprints_list = blueprints_list.unwrap(); - // println!("blueprints list {:?}", blueprints_list.0.keys()); - let mut asset_infos: Vec> = vec![]; - let library_path = - library_override.map_or_else(|| &blenvy_config.library_folder, |l| &l.0); - for (blueprint_name, _) in blueprints_list.0.iter() { - let model_file_name = format!("{}.{}", &blueprint_name, &blenvy_config.format); - let model_path = Path::new(&library_path).join(Path::new(model_file_name.as_str())); - - let model_handle: Handle = asset_server.load(model_path.clone()); - let model_id = model_handle.id(); - let loaded = asset_server.is_loaded_with_dependencies(model_id); - if !loaded { - asset_infos.push(AssetLoadTracker { - name: model_path.to_string_lossy().into(), - id: model_id, - loaded: false, - handle: model_handle.clone(), - }); - } - } - // if not all assets are already loaded, inject a component to signal that we need them to be loaded - if !asset_infos.is_empty() { - commands - .entity(entity) - .insert(AssetsToLoad { - all_loaded: false, - asset_infos, - ..Default::default() - }) - .insert(BlueprintAssetsNotLoaded); - } else { - commands.entity(entity).insert(BlueprintAssetsLoaded); - } - } else { - // in case there are no blueprintsList, we revert back to the old behaviour - commands.entity(entity).insert(BlueprintAssetsLoaded); - } - } -} - -pub(crate) fn blueprints_check_assets_loading( - mut blueprint_assets_to_load: Query< - (Entity, &mut AssetsToLoad), - With, - >, - asset_server: Res, - mut commands: Commands, -) { - for (entity, mut assets_to_load) in blueprint_assets_to_load.iter_mut() { - let mut all_loaded = true; - let mut loaded_amount = 0; - let total = assets_to_load.asset_infos.len(); - for tracker in assets_to_load.asset_infos.iter_mut() { - let asset_id = tracker.id; - let loaded = asset_server.is_loaded_with_dependencies(asset_id); - tracker.loaded = loaded; - if loaded { - loaded_amount += 1; - } else { - all_loaded = false; - } - } - let progress: f32 = loaded_amount as f32 / total as f32; - // println!("progress: {}",progress); - assets_to_load.progress = progress; - - if all_loaded { - assets_to_load.all_loaded = true; - commands - .entity(entity) - .insert(BlueprintAssetsLoaded) - .remove::(); - } - } -} - -pub(crate) fn blueprints_spawn( - spawn_placeholders: Query< - ( - Entity, - &BlueprintName, - Option<&Transform>, - Option<&Parent>, - Option<&Library>, - Option<&AddToGameWorld>, - Option<&Name>, - ), - ( - With, - Added, - Without, - ), - >, - - mut commands: Commands, - mut game_world: Query>, - - assets_gltf: Res>, - asset_server: Res, - blenvy_config: Res, - - children: Query<&Children>, -) { - for ( - entity, - blupeprint_name, - transform, - original_parent, - library_override, - add_to_world, - name, - ) in spawn_placeholders.iter() - { - debug!( - "attempting to spawn {:?} for entity {:?}, id: {:?}, parent:{:?}", - blupeprint_name.0, name, entity, original_parent - ); - - let what = &blupeprint_name.0; - let model_file_name = format!("{}.{}", &what, &blenvy_config.format); - - // library path is either defined at the plugin level or overriden by optional Library components - let library_path = - library_override.map_or_else(|| &blenvy_config.library_folder, |l| &l.0); - let model_path = Path::new(&library_path).join(Path::new(model_file_name.as_str())); - - // info!("attempting to spawn {:?}", model_path); - let model_handle: Handle = asset_server.load(model_path.clone()); // FIXME: kinda weird now - - let gltf = assets_gltf.get(&model_handle).unwrap_or_else(|| { - panic!( - "gltf file {:?} should have been loaded", - model_path.to_str() - ) - }); - - // WARNING we work under the assumtion that there is ONLY ONE named scene, and that the first one is the right one - let main_scene_name = gltf - .named_scenes - .keys() - .next() - .expect("there should be at least one named scene in the gltf file to spawn"); - - let scene = &gltf.named_scenes[main_scene_name]; - - // transforms are optional, but still deal with them correctly - let mut transforms: Transform = Transform::default(); - if transform.is_some() { - transforms = *transform.unwrap(); - } - - let mut original_children: Vec = vec![]; - if let Ok(c) = children.get(entity) { - for child in c.iter() { - original_children.push(*child); - } - } - commands.entity(entity).insert(( - SceneBundle { - scene: scene.clone(), - transform: transforms, - ..Default::default() - }, - Spawned, - OriginalChildren(original_children), - BlueprintAnimations { - // these are animations specific to the inside of the blueprint - named_animations: gltf.named_animations.clone(), - }, - )); - - if add_to_world.is_some() { - let world = game_world - .get_single_mut() - .expect("there should be a game world present"); - commands.entity(world).add_child(entity); - } - } -} diff --git a/crates/blenvy/old/spawn_from_blueprints copy.rs b/crates/blenvy/old/spawn_from_blueprints copy.rs deleted file mode 100644 index cc48648..0000000 --- a/crates/blenvy/old/spawn_from_blueprints copy.rs +++ /dev/null @@ -1,396 +0,0 @@ -use std::path::{Path, PathBuf}; - -use bevy::{gltf::Gltf, prelude::*, utils::hashbrown::HashMap}; -use serde_json::Value; - -use crate::{BlueprintAssets, AssetsToLoad, AssetLoadTracker, BlenvyConfig, BlueprintAnimations, BlueprintAssetsLoaded, BlueprintAssetsNotLoaded}; - -/// this is a flag component for our levels/game world -#[derive(Component)] -pub struct GameWorldTag; - -/// Main component for the blueprints -#[derive(Component, Reflect, Default, Debug)] -#[reflect(Component)] -pub struct BlueprintName(pub String); - -/// path component for the blueprints -#[derive(Component, Reflect, Default, Debug)] -#[reflect(Component)] -pub struct BlueprintPath(pub String); - -/// flag component needed to signify the intent to spawn a Blueprint -#[derive(Component, Reflect, Default, Debug)] -#[reflect(Component)] -pub struct SpawnBlueprint; - -#[derive(Component)] -/// flag component for dynamically spawned scenes -pub struct Spawned; - - -#[derive(Component, Debug)] -/// flag component added when a Blueprint instance ist Ready : ie : -/// - its assets have loaded -/// - it has finished spawning -pub struct BlueprintInstanceReady; - -#[derive(Component, Reflect, Default, Debug)] -#[reflect(Component)] -/// flag component marking any spwaned child of blueprints ..unless the original entity was marked with the `NoInBlueprint` marker component -pub struct InBlueprint; - -#[derive(Component, Reflect, Default, Debug)] -#[reflect(Component)] -/// flag component preventing any spawned child of blueprints to be marked with the `InBlueprint` component -pub struct NoInBlueprint; - -#[derive(Component, Reflect, Default, Debug)] -#[reflect(Component)] -// this allows overriding the default library path for a given entity/blueprint -pub struct Library(pub PathBuf); - -#[derive(Component, Reflect, Default, Debug)] -#[reflect(Component)] -/// flag component to force adding newly spawned entity as child of game world -pub struct AddToGameWorld; - -#[derive(Component)] -/// helper component, just to transfer child data -pub(crate) struct OriginalChildren(pub Vec); - - -#[derive(Event, Debug)] -pub enum BlueprintEvent { - - /// event fired when a blueprint has finished loading its assets & before it attempts spawning - AssetsLoaded { - blueprint_name: String, - blueprint_path: String, - // TODO: add assets list ? - }, - /// event fired when a blueprint is COMPLETELY done spawning ie - /// - all its assets have been loaded - /// - the spawning attempt has been sucessfull - Spawned { - blueprint_name: String, - blueprint_path: String, - }, - - /// - Ready { - blueprint_path: String, - } - -} - - -use gltf::Gltf as RawGltf; - -pub(crate) fn blueprints_prepare_spawn( - spawn_placeholders: Query< - ( - Entity, - &BlueprintPath, - ), - (Added, Without, Without)>, - - // before 0.14 we have to use a seperate query, after migrating we can query at the root level - entities_with_assets: Query< - ( - Entity, - /*&BlueprintName, - &BlueprintPath, - Option<&Parent>,*/ - Option<&Name>, - Option<&BlueprintAssets>, - ), - (Added), // Added - >, - - - bla_bla : Query< - ( - Entity, - &BlueprintName, - &BlueprintPath, - Option<&Parent>, - Option<&BlueprintAssets>, - ),(Added) - >, -mut commands: Commands, -asset_server: Res, - - -) { - for (entity, blueprint_path) in spawn_placeholders.iter() { - println!("added blueprint_path {:?}", blueprint_path); - /*commands.entity(entity).insert( - SceneBundle { - scene: asset_server.load(format!("{}#Scene0", &blueprint_path.0)), // "levels/World.glb#Scene0"), - ..default() - }, - );*/ - // let model_handle: Handle = asset_server.load(model_path.clone()); - } - - for (entity, blueprint_name, blueprint_path, parent, all_assets) in bla_bla.iter() { - println!("added blueprint to spawn {:?} {:?}", blueprint_name, blueprint_path); - println!("all assets {:?}", all_assets); - - - - /* prefetch attempt */ - let gltf = RawGltf::open(format!("assets/{}", blueprint_path.0)).unwrap();// RawGltf::open("examples/Box.gltf")?; - for scene in gltf.scenes() { - let foo_extras = scene.extras().clone().unwrap(); - - let lookup: HashMap = serde_json::from_str(&foo_extras.get()).unwrap(); - for (key, value) in lookup.clone().into_iter() { - println!("{} / {}", key, value); - } - - if lookup.contains_key("BlueprintAssets"){ - let assets_raw = &lookup["BlueprintAssets"]; - println!("ASSETS RAW {}", assets_raw); - let x: BlueprintAssets = ron::from_str(&assets_raw.as_str().unwrap()).unwrap(); - println!("YAHA {:?}", x); - - } - - //println!("SCENE EXTRAS {:?}", foo_extras); - } - - ////////////// - - - let untyped_handle = asset_server.load_untyped(&blueprint_path.0); - let asset_id = untyped_handle.id(); - let loaded = asset_server.is_loaded_with_dependencies(asset_id); - - let mut asset_infos: Vec = vec![]; - if !loaded { - asset_infos.push(AssetLoadTracker { - name: blueprint_name.0.clone(), - id: asset_id, - loaded: false, - handle: untyped_handle.clone(), - }); - } - - // now insert load tracker - if !asset_infos.is_empty() { - commands - .entity(entity) - .insert(AssetsToLoad { - all_loaded: false, - asset_infos, - ..Default::default() - }) - .insert(BlueprintAssetsNotLoaded); - } else { - commands.entity(entity).insert(BlueprintAssetsLoaded); - } - } - - for (child_entity, child_entity_name, all_assets) in entities_with_assets.iter(){ - println!("added assets {:?} to {:?}", all_assets, child_entity_name); - if all_assets.is_some() { - let mut asset_infos: Vec = vec![]; - - for asset in all_assets.unwrap().0.iter() { - let untyped_handle = asset_server.load_untyped(&asset.path); - //println!("untyped handle {:?}", untyped_handle); - //asset_server.load(asset.path); - - let asset_id = untyped_handle.id(); - //println!("ID {:?}", asset_id); - let loaded = asset_server.is_loaded_with_dependencies(asset_id); - //println!("Loaded ? {:?}", loaded); - if !loaded { - asset_infos.push(AssetLoadTracker { - name: asset.name.clone(), - id: asset_id, - loaded: false, - handle: untyped_handle.clone(), - }); - } - } - - // now insert load tracker - if !asset_infos.is_empty() { - commands - .entity(child_entity) - .insert(AssetsToLoad { - all_loaded: false, - asset_infos, - ..Default::default() - }) - .insert(BlueprintAssetsNotLoaded); - } else { - commands.entity(child_entity).insert(BlueprintAssetsLoaded); - } - } - } -} - -pub(crate) fn blueprints_check_assets_loading( - mut blueprint_assets_to_load: Query< - (Entity, Option<&Name>, &BlueprintPath, &mut AssetsToLoad), - With, - >, - asset_server: Res, - mut commands: Commands, - mut blueprint_events: EventWriter, - -) { - for (entity, entity_name, blueprint_path, mut assets_to_load) in blueprint_assets_to_load.iter_mut() { - let mut all_loaded = true; - let mut loaded_amount = 0; - let total = assets_to_load.asset_infos.len(); - for tracker in assets_to_load.asset_infos.iter_mut() { - let asset_id = tracker.id; - let loaded = asset_server.is_loaded_with_dependencies(asset_id); - println!("loading {}: // load state: {:?}", tracker.name, asset_server.load_state(asset_id)); - - // FIXME: hack for now - let mut failed = false;// asset_server.load_state(asset_id) == bevy::asset::LoadState::Failed(_error); - match asset_server.load_state(asset_id) { - bevy::asset::LoadState::Failed(_) => { - failed = true - }, - _ => {} - } - tracker.loaded = loaded || failed; - if loaded || failed { - loaded_amount += 1; - } else { - all_loaded = false; - } - } - let progress: f32 = loaded_amount as f32 / total as f32; - println!("progress: {}",progress); - assets_to_load.progress = progress; - - if all_loaded { - assets_to_load.all_loaded = true; - println!("done with loading {:?}, inserting components", entity_name); - blueprint_events.send(BlueprintEvent::AssetsLoaded {blueprint_name:"".into(), blueprint_path: blueprint_path.0.clone() }); - - commands - .entity(entity) - .insert(BlueprintAssetsLoaded) - .remove::() - .remove::() - ; - } - } -} - - - -pub(crate) fn blueprints_spawn( - spawn_placeholders: Query< - ( - Entity, - &BlueprintName, - &BlueprintPath, - Option<&Transform>, - Option<&Parent>, - Option<&AddToGameWorld>, - Option<&Name>, - ), - ( - With, - Added, - Without, - ), - >, - - mut commands: Commands, - mut game_world: Query>, - - assets_gltf: Res>, - asset_server: Res, - children: Query<&Children>, -) { - for ( - entity, - blupeprint_name, - blueprint_path, - transform, - original_parent, - add_to_world, - name, - ) in spawn_placeholders.iter() - { - info!( - "attempting to spawn blueprint {:?} for entity {:?}, id: {:?}, parent:{:?}", - blupeprint_name.0, name, entity, original_parent - ); - - // info!("attempting to spawn {:?}", model_path); - let model_handle: Handle = asset_server.load(blueprint_path.0.clone()); // FIXME: kinda weird now - - let gltf = assets_gltf.get(&model_handle).unwrap_or_else(|| { - panic!( - "gltf file {:?} should have been loaded", - &blueprint_path.0 - ) - }); - - // WARNING we work under the assumtion that there is ONLY ONE named scene, and that the first one is the right one - let main_scene_name = gltf - .named_scenes - .keys() - .next() - .expect("there should be at least one named scene in the gltf file to spawn"); - - let scene = &gltf.named_scenes[main_scene_name]; - - // transforms are optional, but still deal with them correctly - let mut transforms: Transform = Transform::default(); - if transform.is_some() { - transforms = *transform.unwrap(); - } - - let mut original_children: Vec = vec![]; - if let Ok(c) = children.get(entity) { - for child in c.iter() { - original_children.push(*child); - } - } - - let mut named_animations:HashMap> = HashMap::new() ; - for (key, value) in gltf.named_animations.iter() { - named_animations.insert(key.to_string(), value.clone()); - } - - commands.entity(entity).insert(( - SceneBundle { - scene: scene.clone(), - transform: transforms, - ..Default::default() - }, - Spawned, - BlueprintInstanceReady, // FIXME: not sure if this is should be added here or in the post process - OriginalChildren(original_children), - BlueprintAnimations { - // these are animations specific to the inside of the blueprint - named_animations: named_animations//gltf.named_animations.clone(), - }, - )); - - if add_to_world.is_some() { - let world = game_world - .get_single_mut() - .expect("there should be a game world present"); - commands.entity(world).add_child(entity); - } - } -} - - - - - diff --git a/crates/blenvy/old/spawn_post_process.rs b/crates/blenvy/old/spawn_post_process.rs deleted file mode 100644 index 2479634..0000000 --- a/crates/blenvy/old/spawn_post_process.rs +++ /dev/null @@ -1,165 +0,0 @@ -use std::any::TypeId; - -use bevy::gltf::Gltf; -use bevy::prelude::*; -use bevy::scene::SceneInstance; -// use bevy::utils::hashbrown::HashSet; - -use crate::{BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintInfo, BlueprintReadyForPostProcess, BlueprintInstanceReady, BlueprintSpawning, SubBlueprintSpawnRoot, SubBlueprintsSpawnTracker}; -use crate::{SpawnBlueprint, Spawned}; -use crate::{ - BlueprintEvent, CopyComponents, InBlueprint, NoInBlueprint, OriginalChildren -}; - - - - -/// this system is in charge of doing any necessary post processing after a blueprint scene has been spawned -/// - it removes one level of useless nesting -/// - it copies the blueprint's root components to the entity it was spawned on (original entity) -/// - it copies the children of the blueprint scene into the original entity -/// - it add `AnimationLink` components so that animations can be controlled from the original entity -/// - it cleans up/ removes a few , by then uneeded components -pub(crate) fn spawned_blueprint_post_process( // rename to ' - unprocessed_entities: Query< - ( - Entity, - &Children, - &OriginalChildren, - &BlueprintAnimations, - Option<&NoInBlueprint>, - Option<&Name>, - &BlueprintInfo, - - // sub blueprint instances tracker - Option<&SubBlueprintSpawnRoot> - ), - (With, With, Added), - >, - added_animation_players: Query<(Entity, &Parent), Added>, - all_children: Query<&Children>, - - mut trackers: Query<(Entity, &mut SubBlueprintsSpawnTracker, &BlueprintInfo)>, - - - mut commands: Commands, - mut blueprint_events: EventWriter, - -) { - for (original, children, original_children, animations, no_inblueprint, name, blueprint_info, track_root) in - unprocessed_entities.iter() - { - info!("post processing blueprint for entity {:?}", name); - - if children.len() == 0 { - warn!("timing issue ! no children found, please restart your bevy app (bug being investigated)"); - continue; - } - // the root node is the first & normally only child inside a scene, it is the one that has all relevant components - let mut root_entity = Entity::PLACEHOLDER; //FIXME: and what about childless ones ?? => should not be possible normally - // let diff = HashSet::from_iter(original_children.0).difference(HashSet::from_iter(children)); - // we find the first child that was not in the entity before (aka added during the scene spawning) - for c in children.iter() { - if !original_children.0.contains(c) { - root_entity = *c; - break; - } - } - - // we flag all children of the blueprint instance with 'InBlueprint' - // can be usefull to filter out anything that came from blueprints vs normal children - if no_inblueprint.is_none() { - for child in all_children.iter_descendants(root_entity) { - commands.entity(child).insert(InBlueprint); // we do this here in order to avoid doing it to normal children - } - } - - // copy components into from blueprint instance's root_entity to original entity - commands.add(CopyComponents { - source: root_entity, - destination: original, - exclude: vec![TypeId::of::(), TypeId::of::()], - stringent: false, - }); - - // we move all of children of the blueprint instance one level to the original entity - if let Ok(root_entity_children) = all_children.get(root_entity) { - for child in root_entity_children.iter() { - // info!("copying child {:?} upward from {:?} to {:?}", names.get(*child), root_entity, original); - commands.entity(original).add_child(*child); - } - } - - if animations.named_animations.keys().len() > 0 { - for (added, parent) in added_animation_players.iter() { - if parent.get() == root_entity { - // 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).remove::(); - commands.entity(original).remove::(); - // commands.entity(original).remove::>(); // FIXME: if we delete the handle to the scene, things get despawned ! not what we want - //commands.entity(original).remove::(); // also clear the sub assets tracker to free up handles, perhaps just freeing up the handles and leave the rest would be better ? - //commands.entity(original).remove::(); - commands.entity(root_entity).despawn_recursive(); // Remove the root entity that comes from the spawned-in scene - commands.entity(original).insert( Visibility::Visible - ); - - commands.entity(original) - .insert(BlueprintInstanceReady) - .remove::() - - - // - .remove::() - ; - - - if let Some(track_root) = track_root { - //println!("got some root"); - if let Ok((s_entity, mut tracker, bp_info)) = trackers.get_mut(track_root.0) { - // println!("found the tracker, setting loaded for {}", entity); - tracker.sub_blueprint_instances.entry(original).or_insert(true); - tracker.sub_blueprint_instances.insert(original, true); - - // TODO: ugh, my limited rust knowledge, this is bad code - let mut all_spawned = true; - - for key in tracker.sub_blueprint_instances.keys() { - let val = tracker.sub_blueprint_instances[key]; - println!("Key: {key}, Spawned {}", val); - } - - for val in tracker.sub_blueprint_instances.values() { - println!("spawned {}", val); - if !val { - all_spawned = false; - break; - } - } - if all_spawned { // TODO: move this to an other system, or "notify" the tracked root entity of the fact that all its sub blueprints have been loaded - println!("ALLLLL SPAAAAWNED for {}", track_root.0); - // commands.entity(track_root.0).insert(bundle) - blueprint_events.send(BlueprintEvent::Spawned {entity: track_root.0, blueprint_name: bp_info.name.clone(), blueprint_path: bp_info.path.clone()}); - - } - } - - } - if trackers.get(original).is_err() { - // if it has no sub blueprint instances - blueprint_events.send(BlueprintEvent::Spawned {entity: original, blueprint_name: blueprint_info.name.clone(), blueprint_path: blueprint_info.path.clone()}); - } - - debug!("DONE WITH POST PROCESS"); - info!("done instanciating blueprint for entity {:?}", name); - - } -} diff --git a/crates/blenvy/src/components/mod.rs b/crates/blenvy/src/components/mod.rs index d79a79f..c34212b 100644 --- a/crates/blenvy/src/components/mod.rs +++ b/crates/blenvy/src/components/mod.rs @@ -23,7 +23,7 @@ use bevy::{ /// # use bevy::gltf::*; /// # use bevy_gltf_components::ComponentsFromGltfPlugin; /// -/// //too barebones of an example to be meaningfull, please see https://github.com/kaosat-dev/Blender_bevy_components_workflow/examples/basic for a real example +/// //too barebones of an example to be meaningfull, please see https://github.com/kaosat-dev/Blenvy/examples/basic for a real example /// fn main() { /// App::new() /// .add_plugins(DefaultPlugins) diff --git a/crates/blenvy/src/lib.rs b/crates/blenvy/src/lib.rs index 2a1c654..01bc10b 100644 --- a/crates/blenvy/src/lib.rs +++ b/crates/blenvy/src/lib.rs @@ -22,6 +22,10 @@ pub struct BlenvyConfig { pub(crate) aabbs: bool, pub(crate) aabb_cache: HashMap, // cache for aabbs pub(crate) materials_cache: HashMap>, // cache for materials + + // save & load + pub(crate) save_component_filter: SceneFilter, + pub(crate) save_resource_filter: SceneFilter, } #[derive(Debug, Clone)] @@ -34,6 +38,10 @@ pub struct BlenvyPlugin { /// Automatically generate aabbs for the blueprints root objects pub aabbs: bool, + + // for save & load + pub save_component_filter: SceneFilter, + pub save_resource_filter: SceneFilter, } impl Default for BlenvyPlugin { @@ -43,6 +51,9 @@ impl Default for BlenvyPlugin { registry_component_filter: SceneFilter::default(), registry_resource_filter: SceneFilter::default(), aabbs: false, + + save_component_filter: SceneFilter::default(), + save_resource_filter: SceneFilter::default(), } } } @@ -63,6 +74,9 @@ impl Plugin for BlenvyPlugin { aabb_cache: HashMap::new(), materials_cache: HashMap::new(), + + save_component_filter: self.save_component_filter.clone(), + save_resource_filter: self.save_resource_filter.clone() }); } }