This commit is contained in:
Mark Moissette 2024-07-20 00:25:06 +00:00 committed by GitHub
commit 6c3841969f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
545 changed files with 36213 additions and 17500 deletions

View File

@ -1,11 +1,7 @@
[workspace]
members = [
"crates/*",
"examples/common*",
"examples/bevy_gltf_components/*",
"examples/bevy_gltf_blueprints/*",
"examples/bevy_gltf_save_load/*",
"examples/bevy_registry_export/*",
"examples/blenvy/*",
"testing/bevy_example/",
]
resolver = "2"

143
Migration_guide.md Normal file
View File

@ -0,0 +1,143 @@
# Blender add-ons
- gltf_auto_export and bevy_components have been replaced with a single Blenvy add-on for simplicity
## Components:
- no more need to add your components to an empty called xxx_components, you can now directly add your components to the blueprint's collection itself
- you will need to "upgrade" your components from the previous add-on, as they are stored in a completely different way
- bonus: you can also visualize & edit your blueprint's component directly on each instance
## Multiple components with the same short name
Up until now , it was not possible to have multiple components with the same name (ie foo::bar::componentA & some::other::componentA) as all the logic was based on short names,
this is not an issue anymore
## Auto export:
- the previous stripped down gltf export settings are not part of the add-on anymore, please configure them like this:
- you need to reconfigure your auto export settings , as they have changed significantly as has their storage
## All the Bevy crates have been replaced with a single one
- the new crate doesn't even really need configuring, so
- in your cargo.toml file, replace any references to the old crates (bevy_gltf_components, bevy_gltf_blueprints, bevy_registry_export, bevy_gltf_save_load etc)
with:
```toml
# Cargo.toml
[dependencies]
bevy="0.14"
blenvy = { version = "0.1.0"}
```
and set things up in your code:
```rust no_run
use bevy::prelude::*;
use blenvy::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(BlenvyPlugin)
.run();
}
```
## Removed almost all setting for the crate
- the ONLY setting is **aabbs** // defaults to true
## Legacy mode has been removed
- less headaches when using the tools!
If you still want to manually specify components using Blender's custom properties you need to
## BlueprintName replaced with BlueprintInfo
- this is a very important change ! to avoid possible path clashes , the ```BlueprintInfo``` component contains
the actual path (with your **assets** folder) to the Blueprint, and a name (for convenience)
## SpawnHere renamed to SpawnBlueprint
changed the naming for more clarity & specificity
## Automatic assets loading
- no more need to preload gltf files, you can spawn a level & all its blueprint like this:
```rust no_run
commands.spawn((
BlueprintInfo::from_path("levels/World.gltf"),
HideUntilReady, // Only if you want to keep the level hidden until it is finished spawning
SpawnBlueprint, // See note above
GameWorldTag,
InAppRunning,
));
```
Blenvy will take care of loading all needed blueprints & other assets for you
## Blueprint instance events
- you can now use the ```BlueprintEvent``` to get notified of crucial blueprint instance events
* ```AssetsLoaded```
```rust no run
/// event fired when a blueprint instance has finished loading all of its assets & before it attempts spawning
AssetsLoaded {
entity: Entity,
blueprint_name: String,
blueprint_path: String,
// TODO: add assets list ?
}
```
* ```InstanceReady```
```rust no run
/// event fired when a blueprint instance has completely finished spawning, ie
/// - all its assests have been loaded
/// - all of its child blueprint instances are ready
/// - all the post processing is finished (aabb calculation, material replacements etc)
InstanceReady {
entity: Entity,
blueprint_name: String,
blueprint_path: String,
},
```
## BlueprintInstanceDisabled
you can now query for this component
## Track loading blueprint instances with the BlueprintSpawning component
- other than with events, you can also query for the ```BlueprintSpawning``` component to be sure an entity you are manipulating is finished with its blueprint instance spawning process
## Keep your currently spawning blueprint instances hidden until they are ready with the HideUntilReady component
If you want your blueprint instance to be hidden until it is ready, just add this component to the entity.
This can be particularly usefull in at least two use cases:
- when spawning levels
- when spawning bluprint instances that contain **lights** at runtime: in previous versions I have noticed some very unpleasant "flashing" effect when spawning blueprints with lights,
this component avoids that issue
## Hot reload
if you have configured your Bevy project to use hot reload you will automatically get hot reloading of levels & blueprints
## Improved animation handling
- sceneAnimations
- animationTriggers
## Completely restructured blueprint spawning process
Additionally
- you do not really need to worry about SystemSets anymore

View File

@ -4,11 +4,11 @@ The workflow goes as follows (once you got your Bevy code setup)
## Bevy side
- create & register all your components you want to be able to set from the Blender side (this is basic Bevy, no specific work needed)
- follow the instructions in the [bevy_registry_export](./crates/bevy_registry_export/) to generate a registry export
- follow the instructions in the [blenvy](./crates/blenvy/) to generate a registry export
## Component creation
Setup the Blender [bevy_components](./tools/bevy_components/README.md) add-on
Setup the Blender [Blenvy](./tools/blenvy/README.md) blender add-on
to add & edit your components visually & reliably
![bevy_components](./docs/bevy_components.png)
@ -22,7 +22,7 @@ to add & edit your components visually & reliably
- custom properties
- cameras & lights if you want a complete level (as in this example)
![gltf_export](./docs/gltf_export.png)
- or much better, using [gltf_auto_export](./tools/gltf_auto_export/)
- or much better, using [blenvy](./tools/blenvy/)
## Now use your gltf files in Bevy

100
README.md
View File

@ -2,107 +2,86 @@
[![License](https://img.shields.io/crates/l/bevy_gltf_components)](https://github.com/kaosat-dev/Blender_bevy_components_workflow/blob/main/LICENSE.md)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/F1F5TO32O)
# Blender_bevy_components_workflow
# BLENVY: a friendly Blender <=> Bevy workflow !
![demo](./docs/blender_bevy.png)
Crates & tools for adding components from gltf files in the [Bevy](https://bevyengine.org/) game engine.
It enables minimalistic [Blender](https://www.blender.org/) (gltf) centric workflow for Bevy, ie defining entites & their components
inside Blender using Blender's objects **custom properties**.
Aka "Blender as editor for Bevy"
It enables a [Blender](https://www.blender.org/) (gltf) centric workflow for Bevy, ie defining entites & their components
inside Blender. Aka "Blender as editor for Bevy"
It also allows you to setup 'blueprints' in Blender by using collections (the recomended way to go most of the time), or directly on single use objects .
## Features
* Useful if you want to use Blender (or any editor allowing to export gltf with configurable gltf_extras) as your Editor
* define Bevy components as custom properties in Blender (some visually , some using RON, though an older JSON version is also available)
* no plugin or extra tools needed in Blender (but I provide a [little Blender add-on](./tools/gltf_auto_export/README.md) to auto-export to gltf on save (and more !) if you want !)
* now also with a nice UI tool to add & edit Bevy components in [Blender](./tools/bevy_components/README.md)
* define components in Blender Collections & override any of them in your collection instances if you want
* ability to automatically turn your Blender collections into [gltf Blueprints](./crates/bevy_gltf_blueprints/README.md) for reuse
* Useful if you want to use Blender as your Editor
* define Bevy components as custom properties in Blender with an UI tool to add & edit Bevy components, automatically export gltf blueprints & more in [Blender](./tools/blenvy/README.md)
* blueprints & levels system : turn your Blender collections into [gltf Blueprints](./crates/blenvy/README.md) for reuse inside levels that are just Blender scenes
* setup & tweak components in Blender Collections & override any of them in your collection instances if you want
* setup & tweak components for objects, meshes and materials as well !
* automatically load all assets for each blueprint (gltf files, manually added assets), with no setup required
* hot reload of your levels & blueprints
* minimal setup & code, you can have something basic running fast
* minimal dependencies: Bevy, Serde & Ron only !
* opensource
> If you were previously using the individual bevy_gltf_xxx crates & Blender add-ons please see the [migration guide](./Migration_guide.md)
## Crates
- [bevy_gltf_components](./crates/bevy_gltf_components/) This crate allows you to define components direclty inside gltf files and instanciate/inject the components on the Bevy side.
One crate to rule them all !
- [blenvy](./crates/blenvy/) This crate allows you to
* define components direclty inside gltf files and instanciate/inject the components on the Bevy side.
* export your project's Bevy registry to json, in order to be able to generate custom component UIs on the Blender side in the Blender [blenvy](./tools/blenvy/README.md) add-on
* define Blueprints/Prefabs for Bevy inside gltf files and spawn them in Bevy. With the ability to override and add components when spawning, efficient "level" loading etc
* the ability to save & load your game state in a relatively simple way, by leveraging the blueprint functionality to only save a minimal subset of dynamic data, seperating dynamic & static parts of levels etc.
OLD videos:
There is a [video tutorial/explanation](https://youtu.be/-lcScjQCA3c) if you want, or you can read the crate docs.
The examples for the crate are [here](./examples/bevy_gltf_components/)
- [bevy_gltf_blueprints](./crates/bevy_gltf_blueprints/) This crate adds the ability to define Blueprints/Prefabs for Bevy inside gltf files and spawn them in Bevy. With the ability to override and add components when spawning, efficient "level" loading etc
There is a [video tutorial/explanation](https://youtu.be/CgyNtwgYwdM) for this one too, or you can read the crate docs
The examples for the crate are [here](./examples/bevy_gltf_blueprints/)
> Note: this is the recomended crate to use and uses ```bevy_gltf_components``` under the hood
- [bevy_gltf_save_load](./crates/bevy_gltf_save_load/) This crate adds the ability to save & load your game state in a relatively simple way, by leveraging the blueprint functionality of
bevy_gltf_blueprints to only save a minimal subset of dynamic data, seperating dynamic & static parts of levels etc.
The examples for the crate are [here](./examples/bevy_gltf_save_load/)
> Note: this uses ```bevy_gltf_blueprints``` under the hood
- [bevy_registry_export](./crates/bevy_registry_export/) This crate adds the ability to export your project's Bevy registry to json, in order to be able to generate custom component UIs on the Blender side in the Blender [bevy_components](./tools/bevy_components/README.md) add-on
The examples for the crate are [here](./examples/blenvy/)
## Tools
### Blender: gltf_auto_export
- for convenience I also added a [Blender addon](./tools/gltf_auto_export/README.md) that automatically exports your level/world from Blender to gltf whenever you save your Blend file
- it also supports automatical exports of collections as [Gltf blueprints](./crates/bevy_gltf_blueprints/README.md) & more !
Please read the [README]((./tools/gltf_auto_export/README.md)) of the add-on for installation & use instructions
### Blender: bevy_components
- an add-on for Blender to allow easilly adding & editing Bevy components , using automatically generated UIs for each component
Please read the [README]((./tools/bevy_components/README.md)) of the add-on for installation & use instructions
### Blender: blenvy
- an all in one [Blender addon](./tools/blenvy/README.md) for the Blender side of the workflow:
- allow easilly adding & editing Bevy components , using automatically generated UIs for each component
- automatically exports your level/world from Blender to gltf whenever you save your Blend file
- automatically export your [Gltf blueprints](./crates/blenvy/README.md) & assets
## Examples
you can find all examples, by crate as seperate crates for clearer dependencies in [here](./examples/)
- [bevy_gltf_components](./examples/bevy_gltf_components/)
* [basic](./examples/bevy_gltf_components/basic/) use of ```bevy_gltf_components``` only, to spawn entities with components defined inside gltf files
- [bevy_gltf_blueprints](./examples/bevy_gltf_blueprints/)
* [basic](./examples/bevy_gltf_blueprints/basic/) more advanced example : use of ```bevy_gltf_blueprints``` to spawn a level and then populate it with entities coming from different gltf files, live (at runtime) spawning of entities etc
* [animation](./examples/bevy_gltf_blueprints/animation/) how to use and trigger animations from gltf files (a feature of ```bevy_gltf_blueprints```)
* & lots more
- [bevy_gltf_save_load](./examples/bevy_gltf_save_load/)
- [bevy_registry_export](./examples/bevy_registry_export/)
you can find all examples, [here](./examples/blenvy)
* [components](./examples/blenvy/components/) use of ```components``` only, to spawn entities with components defined inside gltf files
* [blueprints](./examples/blenvy/blueprints/) use of ```blueprints``` and ```levels``` to spawn a level and then populate it with entities coming from different gltf files, live (at runtime) spawning of entities etc
* [animation](./examples/blenvy/animation/) how to use and trigger animations from gltf files
* [save_load](./examples/blenvy/save_load/) how to save & load levels
* [demo](./examples/demo/) a full demo showcasing all features , including physics, animation
## Workflow
The workflow goes as follows (once you got your Bevy code setup)
- create & register all your components you want to be able to set from the Blender side (this is basic Bevy, no specific work needed)
![component registration](./docs/component_registration.png)
- then you have two options
- add your components to objects in Blender **manually** as **custom properties** : see [here](./README-workflow-classic.md) for more details
- add your components to objects in Blender **with a nice UI** see [here](./README-workflow-ui.md) for more details
- setup & then use the Blenvy [Bevy crate](./crates/blenvy/README.md)
- setup & then use the Blenvy [Blender add-on](./tools/blenvy/README.md)
- iterate
- have fun !
- then add your components to objects in Blender **with a nice UI** see [here](./README-workflow-ui.md) for more details
## Limitations / issues
- Some of `bevy_rapier`/physics code / ways to define colliders could perhaps be done better/visually within Blender (currently it also goes via RON)
## Future work
- I have a number of other tools/ code helpers that I have not yet included here, because they need cleanup/ might make this example too complex
## Credits
- somebody I cannot recall helped me originally with the gltf loading tracker in the Bevy Discord, so thanks ! And if it was you, please let me know so I can give credit where credit is due :)
- Some of `avian` or `bevy_rapier` /physics code / ways to define colliders could perhaps be done better/visually within Blender
## Contributors
@ -114,6 +93,7 @@ Thanks to all the contributors helping out with this project ! Big kudos to you,
- [yukkop](https://github.com/yukkop)
- [killercup](https://github.com/killercup)
- [janhohenheim ](https://github.com/janhohenheim)
- [BUGO07](https://github.com/BUGO07)
## License

36
RELEASE_NOTES.md Normal file
View File

@ -0,0 +1,36 @@
This new release has a lot of breaking changes in the Blender tooling as well as in the Bevy crates
Here is a rundown + rationale behing the changes:
- Blender add-ons
- Auto_export:
- spliting out of gltf exporter settings
up until now , the auto export add-on provided a *subset* of gltf settings, causing issues due to lack of features (in particular for animation settings),
and it required me to play catch up every time there where added / changed settings
the Ui of Blender's gltf exporter is NOT reusable in any form or way inside another add-on . I tried various solutions such as turning the auto exporter
into a 'extension' of the standard exporter, but none of them worked in a reliable or 'nice to use' way
So I decided to split the gltf settings from the auto_export setting, this has multiple advantages:
* access to ALL gltf settings
* the gltf UI & settings will always keep up with the official releases
* less maintenance work
The only disadvantage is that the standard gltf exporter is a normal exporter, so it generates a 'fake' gltf file that immediatly gets deleted after export,
so you will need to use the new side panel to set your gltf settings:
* this is also done to ensure that the gltf settings settings used for auto export are NOT interfering with the ones you might use when exporting gltf files normally
- change detection :
after spending MANY hours analysing the issues with change detection (using some of Blender's built in logic) I came to the conclusion that it was not going to be reliable enough, so I opted for a 'hand made' brute force approach to change detection:
* every time you save (& thus export), all your scenes are serialized / hashed and that hashed version is compared to the one from the last save to determine what changed and what did not
- handling of external/ embeded / split etc collections
while adding tests I also realised that the detection of which main scenes & blueprints that needed to be exported was faulty, so I rewrote all the code in charge of that : this means that in general , based on your settings, the add-on will more accuratly export only those levels/blueprints that really NEED to be exported
- improved handling of multi-blend file projects
Up until now, all export paths where relative ** to the blend file itself** which could lead to issues when working with multiple blend files
Also for future improvements regarding assets managment, I changed the export paths to be relative to a new "project root" folder which is your *Bevy project's root folder*
- the levels/worlds now also got a seperate setting so you can easilly set where to export them too (they are not dumped out into the main export folder anymore), giving you more control over your non blueprint exports
- bevy_components
Up until now , it was not possible to have multiple components with the same name (ie ) as all the logic was based on short names
This required completely changing HOW/WHERE components are stored in objects, and they are now stored inside a 'bevy_components' custom property

319
TODO.md Normal file
View File

@ -0,0 +1,319 @@
Auto export
- [x] the original blueprints & levels path are now left as is, and there is an auto injection of xxxpath_full for absolute paths
- [x] replace all uses of the paths with the correct ones above
- [x] levels
- [x] blueprints
- [x] materials
- [x] move out the UI for "assets" folder out of "blueprints condition"
- [x] fix asset path calculations
- root path => relative to blend file path
- asset path => relative to root path
- blueprints/levels/blueprints path => relative to assets path
- [ ] add error handling for de/serialization of project, so that in case of error, the previous saved serialized project is thrown away
- move out some parameters from auto export to a higher level (as they are now used in multiple places)
- [x] main/ library scene names
- [x] paths
- [x] Data storage for custom properties:
- for scenes (main scenes)
- at scene level
- for blueprints
- at collection level
- Note: these should be COPIED to the scene level when exporting, into the temp_scene's properties
> NOTE: UP until we manage to create a PR for Bevy to directly support the scene level gltf_extras, the auto exporter should automatically create (& remove)
any additional object with scene_<scene_name>_components to copy that data to
Assets:
- blueprint assets should be auto_generated & inserted into the list of assets : these assets are NOT removable by the user
- should not change the list of manually added assets
- [x] store assets
- [x] per main scene for level/world assets
- [x] per blueprint for blueprint in lib scene
- [x] UI:
- [x] we need to display all direct assets (stored in the scene)
- [ ] indirect assets:
- QUESTION : do we want to include them in the list of assets per level ?
- this would enable pre-loading ALL the assets, but is not ideal in most other cases
- so add an option ?
- [x] the assets of local blueprints
Blueprints:
- [x] on save: write IN THE COLLECTION PROPERTIES
- list of assets
- export path
- [x] blueprint selection for nested blueprints is broken
- [ ] scan & inject on load
- [ ] scan & inject on save
- [ ] decide where & when to do & store blueprints data
Components:
- [x] add support for adding components to collections
- [x] upgrade all operators:
- [x] add
- [x] remove
- [x] copy & paste
- [x] BLENVY_OT_component_rename_component
- [x] BLENVY_OT_component_fix
- [x] add handling for core::ops::Range<f32> & other ranges
- [x] fix is_component_valid that is used in blenvy
- [x] Hashmap Support
- [x] fix parsing of keys's type either on Bevy side (prefered) or on the Blender side
- [x] fix weird issue with missing "0" property when adding new entry in empty hashmap => happens only if the values for the "setter" have never been set
- [ ] handle missing types in registry for keys & values
- [x] adding a hashmap nukes every existing component ??
- [x] Add correct upgrade handling from individual component to bevy_components
- [x] Settings handling:
- [x] move saveable settings out to a settings file
- [x] update save & load
- [x] add handling of polling frequency & enabling
- [x] move advanced tools to components tab
- [x] remove most of the (bulk) advanced tools, too complex, too unclear (even for me !) and of limited use
- component renaming should be kept, but perhaps simplified:
- if a renaming fails because the parameters are incompatible, nuke the old parameters
- perhaps just add a display list of all NON component custom properties, so the user can find them easilly ?
- [x] status "unregistered" is often false and misleading
-> see in registry ui "for custom_property in object.keys():"
- [x] overhaul / improve the component selector (with built in searching, etc)
- [x] remove select_component_name_to_replace
- [x] display of invalid components is not working ?
- [x] weird items are present in the components list that do not seem to be components
- [x] remove :
- BLENVY_OT_component_list_add_item
- BLENVY_OT_component_list_remove_item
- BLENVY_OT_component_list_select_item: merge it into the rest of the actions
- [x] clearing invalid flag after a registry change does not work correctly (ie the ui still says the component is invalid)
- [x] should reset ALL "invalid" flags IF they have the matching data
- [x] registry auto reload not working ?
- [x] changing the registry breaks all the values of existing components !!!!!!
-> VERY likely due to the int-offset computation for hashes of components
- now switched to tiger_hash
- [x] add warning about hash colision (not much we can/ could do if it is the case ?)
- [x] double check weird collisions AND/OR reuse existing if applicable
- [x] annoying default path for registry, should be relative to the assets path
General things to solve:
- [x] save settings
- [x] load settings
- [x] add blueprints data
- [x] rename all path stuff using the old naming convention : "blueprints_path_full"
- [x] generate the full paths directly when setting them in the UI
- [x] problem : how to deal with defaults: do it on start/load ?
- [x] filter out scenes that have already been used in scenes list
General issues:
- there is no safeguard for naming collisions for naming across blender files
- this can cause an issue for assets list "parent"
- "parents" can only be blueprints
- they normally need/have unique export paths (otherwise, user error, perhaps show it ?)
- perhaps a simple hashing of the parent's path would be enought
- [x] addon-prefs => settings
- [x] generate_gltf_export_settings => should not use add-on prefs at all ? since we are not overriding gltf settings that way anymore ?
- [x] remove hard coded path for standard gltf settings
- [x] load settings on file load
- [x] auto_export
- [x] components
- [x] add handling of errors when trying to load settings
- [x] fix auto export workflow
- [x] add hashing of modifiers/ geometry nodes in serialize scene
- [x] add ability to FORCE export specific blueprints & levels
- [x] change scene selector to work on actual scenes aka to deal with renamed scenes
- [x] remove get_main_and_library_scenes as it should not be needed anymore
- [x] fix asset file selection
- [x] change "assets" tab to "levels"/worlds tab & modify UI accordingly
- [x] remove local assets, useless
- [x] remove 'export_marked_assets' it should be a default setting
- [x] disable/ hide asset editing ui for external assets
- [x] fix level asets UI
- [x] remove BlueprintsList & replace is with assets list
- [x] switch to bevy 0.14 rc2
- [x] trigger events when assets are loaded, blueprints are spawned & co
- [x] overall cleanup
- [x] object.add_bevy_component => blenvy.component_add
Blender side:
- [x] force overwrite of settings files instead of partial updates ?
- [x] prevent loop when loading/setting/saving settings
- [x] fix asset changes not being detected as a scene change
- [x] fix scene setting changes not being detected as a scene change
- [x] add back lighting_components
- [x] check if scene components are being deleted through our scene re-orgs in the spawn post process
- [x] fix unreliable project hashing between sessions: (note, it is due to the use of hash() : https://stackoverflow.com/questions/27522626/hash-function-in-python-3-3-returns-different-results-between-sessions)
- [x] figure out why there are still changes per session (it is due to object pointer being present in the generated "hash")
- materials & modifiers, both using the same underlying logic
- [x] filter out components_meta
- [x] filter out xxx_ui propgroups
- [x] fix missing main/lib scene names in blenvy_common_settings
- [x] fix incorect updating of main/lib scenes list in settings
- [ ] add handling of scene renames
- [x] store (on load) a mapping of scene objects to scene names
- [x] on save, calculate another mapping of scene objects to scene names
- if there is a mismatch between the stored version & the new version for a given scene, it has been renamed !
- [x] pass this information to scene diffing to remap old/new scene names
- [ ] move the rename detection to AFTER scene serialization, otherwise we could have a naming mistmatch
- weird behaviour, perhaps find another way , ie for example replace scene name in saved previous data
- is post save causing the issue ? review
- [x] investigate weird issue of changes detected to all after a reload
- [x] should we write the previous _xxx data only AFTER a sucessfull export only ?
- [x] finer grained control of setting changes to trigger a re-export:
- [x] common: any of them should trigger
- [x] components: none
- [x] auto_export:
- auto_export: yes
- gltf settings: yes
- change detection: no ?
- export blueprints: YES
- export split dynamic/static: YES
- export merge mode : YES
- materials: YES
- [x] blenvy tooling not appearing in library scenes ?? (edit: was actually , it was not appearing in anything but object mode)
- [x] find a solution for the new color handling
- [x] in theory, srgba, linearrgba , and hsva should be able to be represented visually
- [x] bevy_render::color::Color => bevy_color::color::Color
- [x] fix weird issue with hashmaps with enums as values
- [x] prevent attempting to add unexisting components to targets (ie when using the component search)
- [x] also for the bulk fix actions
- [x] selection of nested objects in collections IS NOT WORKING !!! AHH
- [x] fix/ overhaul upgreadable components
- [x] add listing of upgradeable components for
- [x] meshes
- [x] materials
- [x] fix display of upgradeaeble components & co
- [x] add clear visual distinction between internal (selectable) & non selectable ones
- [x] do not make selection button available for external blueprints/collections
- [x] perhaps do not show the other buttons & inputs either ? we cannot change the values of an external library file anyway
- [x] BLENVY_OT_item_select is missing handling for the other types (outside of object & collection)
- [x] fix selection logic
- [x] update testing blend files
- [x] disable 'export_hierarchy_full_collections' for all cases: not reliable and redudant
- [x] fix systematic material exports despite no changes
- [x] investigate lack of detection of changes of adding/changing components
- [x] change scene serialization to account for collections ...sigh
- [x] also add one NOT PER scene for materials, to fix the above issue with materials
- [x] move material caching into hash material
- [x] also remove ____dummy____.bin when export format is gltf
- [ ] fix/cleanup asset information injection (also needed for hot reload)
- [ ] add back per blueprint assets
- [ ] reuse the already existing asset_scan + export thing
- thoughts:
- the "list of all assets" is actually the "fake"/generated one: nobody would write a list of assets for sub assets,
you would just add the assets to your blueprint
- in Bevy at spawning we have
blueprint => assets
for hot reload we need
asset => blueprint instances so we can despawn/respawn etc blueprint instances when one of their assets has changed
problem of untyped vs typed
perhaps have a mapping of untyped => typed id
map asset id => [entity ids]
- [ ] add option to 'split out' meshes from blueprints ?
- [ ] ie considering meshletts etc , it would make sense to keep blueprints seperate from purely mesh gltfs
- [ ] persist exported materials path in blueprints so that it can be read from library file users
- [ ] just like "export_path" write it into each blueprint's collection
- [ ] scan for used materials per blueprint !
- [ ] for scenes, scan for used materials of all non instance objects (TODO: what about overrides ?)
- [ ] add a way of visualizing per blueprint instances ?
- [ ] display export path of blueprints (mostly external) ?
- [ ] hidden objects/collections only semi respected at export
- this is because blueprints are external ?
- [ ] verify based on gltf settings
- [ ] add "Visibility::Hidden" component otherwise
https://devtalk.blender.org/t/how-to-get-render-visibility-for-object/23717
- [ ] inject_export_path_into_internal_blueprints should be called on every asset/blueprint scan !! Not just on export
- [ ] undo after a save removes any saved "serialized scene" data ? DIG into this
- [ ] add tests for
- [ ] disabled components
- [ ] blueprint instances as children of blueprint instances
- [ ] blueprint instances as children of empties
- [x] check/ fix behaviour of blender plugin if all folders are the same (ie, all in assets for example)
- [x] rename all "main scene xx" to "level scene"
- [x] make sure the "add scene" button is not available unless you have actually selected one
- [x] make auto export be on by default, however bail out early by detecting if there are any level/blueprint scenes
Bevy Side:
- [x] deprecate BlueprintName & BlueprintPath & use BlueprintInfo instead
- [x] make blueprint instances invisible until spawning is done to avoid "spawn flash"?
- [x] make this controlable via an additional "HideUntilReady" component
- [x] register "HideUntilReady" so users can set this on their blueprints in Blender directly
- [x] restructure blueprint spawning
- [x] "blueprint ready" only be triggered after all its sub blueprints are ready
- [x] "blueprintInstance ready"/finished
BlueprintAssetsLoaded
BlueprintSceneSpawned
BlueprintChildrenReady
BlueprintReadyForPostProcess
- [x] fix issues with deeply nested blueprints
- perhaps reverse logic by using iter_ascendants
- [x] fix materials handling
- [ ] fix animations handling
- [x] fix/upgrade blueprint level animations
- [x] fix/upgrade scene level animations
- [ ] rename SceneAnimations to LevelAnimations (more coherent with the rest)
- [ ] add back & cleanup animation frame triggers
- [x] move sub blueprint handling to blueprints_finalize_instances
- [x] look into component overriding , it seems broken:
- [x] blueprint level/ collection level components are now visible in instances in Blender
- [x] they do not seem to be transfered to the (instance) entity above:
could they be on the "empty node" ?
- [ ] simplify testing example:
- [x] remove use of rapier physics (or even the whole common boilerplate ?)
- [ ] remove/replace bevy editor pls with some native ui to display hierarchies
- [ ] a full fledged demo (including physics & co)
- [ ] other examples without interactions or physics
- [ ] add hot reloading
- [x] basics
- [x] make it enabled/disabled based on general flag
- [x] account for changes impact both parent & children (ie "world" and "blueprint3") for example, which leads to a crash as there is double despawn /respawn so we need to filter things out
- [x] if there are many assets/blueprints that have changed at the same time, it causes issues similar to the above, so apply a similar fix
- [x] also ignore any entities currently spawning (better to loose some information, than cause a crash)
- [x] for sub blueprint tracking: do not propagate/ deal with parent blueprints if they are not themselves Spawning (ie filter out by "BlueprintSpawning")
- [x] cleanup internals
- [ ] analyse what is off with blueprint level components
- [ ] add the root blueprint itself to the assets either on the blender side or on the bevy side programatically
- [ ] invalidate despawned entity & parent entities AABB
- [ ] add unloading/cache removal of materials
- [ ] add back and upgrade save-load
- [x] review & change general component insertion & spawning ordering & logic
- GltfComponentsSet::Injection => GltfBlueprintsSet::Spawn => GltfBlueprintsSet::AfterSpawn
Injection => inject lights & co => spawn => afterSpawn
=> Injection => inject lights & co
- [ ] add a way of overriding assets for collection instances => doubt this is possible
- [ ] cleanup all the spurious debug messages
- [x] fix animation handling
- [x] how to deal with animation graphs ?
- [x] remove "Library" component & co
- [x] make "InBlueprint" non optional,
- [x] and perhaps rename it to "FromBlueprint"
- [ ] perhaps change it to FromBlueprint(BlueprintInfo)
- [x] BlueprintInstanceDisabled => BlueprintInstanceDisabled
- [x] fix "remove component" operator from the rename/fix/update components panel
- [ ] replace string in BlueprintInfo path with PathBuf ?
- [ ] update main docs
- [x] rename project to Blenvy
- [ ] replace all references to the old 2 add-ons with those to Blenvy
- [x] rename repo to "Blenvy"
- [x] do a deprecation release of all bevy_gltf_xxx crates to point at the new Blenvy crate
clear && pytest -svv --blender-template ../../testing/bevy_example/art/testing_library.blend --blender-executable /home/ckaos/tools/blender/blender-4.1.0-linux-x64/blender tests/test_bevy_integration_prepare.py && pytest -svv --blender-executable /home/ckaos/tools/blender/blender-4.1.0-linux-x64/blender tests/test_bevy_integration.py

View File

@ -1,21 +0,0 @@
[package]
name = "bevy_gltf_blueprints"
version = "0.11.0"
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"
repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
keywords = ["gamedev", "bevy", "gltf", "blueprint", "prefab"]
categories = ["game-development"]
edition = "2021"
license = "MIT OR Apache-2.0"
[lints]
workspace = true
[dependencies]
bevy_gltf_components = { version = "0.6", path = "../bevy_gltf_components" }
bevy = { version = "0.14", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "bevy_animation", "animation"] }
[dev-dependencies]
bevy = { version = "0.14", default-features = false, features = ["dynamic_linking"] }

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Mark "kaosat-dev" Moissette
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,18 +0,0 @@
use bevy::prelude::*;
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`
pub struct Animations {
pub named_animations: HashMap<String, Handle<AnimationClip>>,
pub named_indices: HashMap<String, AnimationNodeIndex>,
pub graph: Handle<AnimationGraph>,
}
#[derive(Component, Debug)]
/// Stop gap helper component : this is inserted into a "root" entity (an entity representing a whole gltf file)
/// so that the root entity knows which of its children contains an actualy `AnimationPlayer` component
/// this is for convenience, because currently , Bevy's gltf parsing inserts `AnimationPlayers` "one level down"
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
pub struct AnimationPlayerLink(pub Entity);

View File

@ -1,179 +0,0 @@
pub mod spawn_from_blueprints;
pub use spawn_from_blueprints::*;
pub mod spawn_post_process;
pub(crate) use spawn_post_process::*;
pub mod animation;
pub use animation::*;
pub mod aabb;
pub use aabb::*;
pub mod materials;
pub use materials::*;
pub mod copy_components;
pub use copy_components::*;
use core::fmt;
use std::path::PathBuf;
use bevy::{
prelude::*,
render::{primitives::Aabb, view::VisibilitySystems},
utils::HashMap,
};
use bevy_gltf_components::{ComponentsFromGltfPlugin, GltfComponentsSet};
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
/// set for the two stages of blueprint based spawning :
pub enum GltfBlueprintsSet {
Spawn,
AfterSpawn,
}
#[derive(Bundle)]
pub struct BluePrintBundle {
pub blueprint: BlueprintName,
pub spawn_here: SpawnHere,
}
impl Default for BluePrintBundle {
fn default() -> Self {
BluePrintBundle {
blueprint: BlueprintName("default".into()),
spawn_here: SpawnHere,
}
}
}
#[derive(Clone, Resource)]
pub struct BluePrintsConfig {
pub(crate) format: GltfFormat,
pub(crate) library_folder: PathBuf,
pub(crate) aabbs: bool,
pub(crate) aabb_cache: HashMap<String, Aabb>, // cache for aabbs
pub(crate) material_library: bool,
pub(crate) material_library_folder: PathBuf,
pub(crate) material_library_cache: HashMap<String, Handle<StandardMaterial>>,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
pub enum GltfFormat {
#[default]
GLB,
GLTF,
}
impl fmt::Display for GltfFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GltfFormat::GLB => {
write!(f, "glb",)
}
GltfFormat::GLTF => {
write!(f, "gltf")
}
}
}
}
#[derive(Debug, Clone)]
/// Plugin for gltf blueprints
pub struct BlueprintsPlugin {
pub legacy_mode: bool, // flag that gets passed on to bevy_gltf_components
pub format: GltfFormat,
/// The base folder where library/blueprints assets are loaded from, relative to the executable.
pub library_folder: PathBuf,
/// Automatically generate aabbs for the blueprints root objects
pub aabbs: bool,
pub material_library: bool,
pub material_library_folder: PathBuf,
}
impl Default for BlueprintsPlugin {
fn default() -> Self {
Self {
legacy_mode: true,
format: GltfFormat::GLB,
library_folder: PathBuf::from("models/library"),
aabbs: false,
material_library: false,
material_library_folder: PathBuf::from("materials"),
}
}
}
fn aabbs_enabled(blueprints_config: Res<BluePrintsConfig>) -> bool {
blueprints_config.aabbs
}
fn materials_library_enabled(blueprints_config: Res<BluePrintsConfig>) -> bool {
blueprints_config.material_library
}
impl Plugin for BlueprintsPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(ComponentsFromGltfPlugin {
legacy_mode: self.legacy_mode,
})
.register_type::<BlueprintName>()
.register_type::<MaterialInfo>()
.register_type::<SpawnHere>()
.register_type::<Animations>()
.register_type::<BlueprintsList>()
.register_type::<Vec<String>>()
.register_type::<HashMap<String, Vec<String>>>()
.insert_resource(BluePrintsConfig {
format: self.format,
library_folder: self.library_folder.clone(),
aabbs: self.aabbs,
aabb_cache: HashMap::new(),
material_library: self.material_library,
material_library_folder: self.material_library_folder.clone(),
material_library_cache: HashMap::new(),
})
.configure_sets(
Update,
(GltfBlueprintsSet::Spawn, GltfBlueprintsSet::AfterSpawn)
.chain()
.after(GltfComponentsSet::Injection),
)
.add_systems(
Update,
(
(
prepare_blueprints,
check_for_loaded,
spawn_from_blueprints,
apply_deferred,
)
.chain(),
(compute_scene_aabbs, apply_deferred)
.chain()
.run_if(aabbs_enabled),
apply_deferred,
(
materials_inject,
check_for_material_loaded,
materials_inject2,
)
.chain()
.run_if(materials_library_enabled),
)
.chain()
.in_set(GltfBlueprintsSet::Spawn),
)
.add_systems(
PostUpdate,
(spawned_blueprint_post_process, apply_deferred)
.chain()
.in_set(GltfBlueprintsSet::AfterSpawn)
.before(VisibilitySystems::CheckVisibility),
);
}
}

View File

@ -1,201 +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, AssetsToLoad, BluePrintsConfig};
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// struct containing the name & source of the material to apply
pub struct MaterialInfo {
pub name: String,
pub source: 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(
blueprints_config: ResMut<BluePrintsConfig>,
material_infos: Query<(Entity, &MaterialInfo), Added<MaterialInfo>>,
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
for (entity, material_info) in material_infos.iter() {
let model_file_name = format!(
"{}_materials_library.{}",
&material_info.source, &blueprints_config.format
);
let materials_path = Path::new(&blueprints_config.material_library_folder)
.join(Path::new(model_file_name.as_str()));
let material_name = &material_info.name;
let material_full_path = materials_path.to_str().unwrap().to_string() + "#" + material_name; // TODO: yikes, cleanup
if blueprints_config
.material_library_cache
.contains_key(&material_full_path)
{
debug!("material is cached, retrieving");
blueprints_config
.material_library_cache
.get(&material_full_path)
.expect("we should have the material available");
commands
.entity(entity)
.insert(BlueprintMaterialAssetsLoaded);
} else {
let material_file_handle: Handle<Gltf> = asset_server.load(materials_path.clone());
let material_file_id = material_file_handle.id();
let asset_infos: Vec<AssetLoadTracker<Gltf>> = vec![AssetLoadTracker {
name: material_full_path,
id: material_file_id,
loaded: false,
handle: material_file_handle.clone(),
}];
commands
.entity(entity)
.insert(AssetsToLoad {
all_loaded: false,
asset_infos,
..Default::default()
})
.insert(BlueprintMaterialAssetsNotLoaded);
/**/
}
}
}
// TODO, merge with check_for_loaded, make generic ?
pub(crate) fn check_for_material_loaded(
mut blueprint_assets_to_load: Query<
(Entity, &mut AssetsToLoad<Gltf>),
With<BlueprintMaterialAssetsNotLoaded>,
>,
asset_server: Res<AssetServer>,
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::<BlueprintMaterialAssetsNotLoaded>();
}
}
}
/// system that injects / replaces materials from material library
pub(crate) fn materials_inject2(
mut blueprints_config: ResMut<BluePrintsConfig>,
material_infos: Query<
(&MaterialInfo, &Children),
(
Added<BlueprintMaterialAssetsLoaded>,
With<BlueprintMaterialAssetsLoaded>,
),
>,
with_materials_and_meshes: Query<
(),
(
With<Parent>,
With<Handle<StandardMaterial>>,
With<Handle<Mesh>>,
),
>,
assets_gltf: Res<Assets<Gltf>>,
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
for (material_info, children) in material_infos.iter() {
let model_file_name = format!(
"{}_materials_library.{}",
&material_info.source, &blueprints_config.format
);
let materials_path = Path::new(&blueprints_config.material_library_folder)
.join(Path::new(model_file_name.as_str()));
let material_name = &material_info.name;
let material_full_path = materials_path.to_str().unwrap().to_string() + "#" + material_name; // TODO: yikes, cleanup
let mut material_found: Option<&Handle<StandardMaterial>> = None;
if blueprints_config
.material_library_cache
.contains_key(&material_full_path)
{
debug!("material is cached, retrieving");
let material = blueprints_config
.material_library_cache
.get(&material_full_path)
.expect("we should have the material available");
material_found = Some(material);
} else {
let model_handle: Handle<Gltf> = asset_server.load(materials_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_name as &str) {
let material = mat_gltf
.named_materials
.get(material_name as &str)
.expect("this material should have been loaded");
blueprints_config
.material_library_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_name,
materials_path.clone()
);
commands.entity(*child).insert(material.clone());
}
}
}
}
}

View File

@ -1,312 +0,0 @@
use std::path::{Path, PathBuf};
use bevy::{gltf::Gltf, prelude::*, utils::HashMap};
use crate::{Animations, BluePrintsConfig};
/// 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);
/// flag component needed to signify the intent to spawn a Blueprint
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct SpawnHere;
#[derive(Component)]
/// flag component for dynamically spawned scenes
pub struct Spawned;
#[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<Entity>);
/// helper component, is used to store the list of sub blueprints to enable automatic loading of dependend blueprints
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct BlueprintsList(pub HashMap<String, Vec<String>>);
/// helper component, for tracking loaded assets's loading state, id , handle etc
#[derive(Default, Debug)]
pub(crate) struct AssetLoadTracker<T: bevy::prelude::Asset> {
#[allow(dead_code)]
pub name: String,
pub id: AssetId<T>,
pub loaded: bool,
#[allow(dead_code)]
pub handle: Handle<T>,
}
/// helper component, for tracking loaded assets
#[derive(Component, Debug)]
pub(crate) struct AssetsToLoad<T: bevy::prelude::Asset> {
pub all_loaded: bool,
pub asset_infos: Vec<AssetLoadTracker<T>>,
pub progress: f32,
}
impl<T: bevy::prelude::Asset> Default for AssetsToLoad<T> {
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<BlueprintName>, Added<SpawnHere>, Without<Spawned>),
>,
mut commands: Commands,
asset_server: Res<AssetServer>,
blueprints_config: Res<BluePrintsConfig>,
) {
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<AssetLoadTracker<Gltf>> = vec![];
let library_path =
library_override.map_or_else(|| &blueprints_config.library_folder, |l| &l.0);
for (blueprint_name, _) in blueprints_list.0.iter() {
let model_file_name = format!("{}.{}", &blueprint_name, &blueprints_config.format);
let model_path = Path::new(&library_path).join(Path::new(model_file_name.as_str()));
let model_handle: Handle<Gltf> = 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 check_for_loaded(
mut blueprint_assets_to_load: Query<
(Entity, &mut AssetsToLoad<Gltf>),
With<BlueprintAssetsNotLoaded>,
>,
asset_server: Res<AssetServer>,
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::<BlueprintAssetsNotLoaded>();
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn spawn_from_blueprints(
spawn_placeholders: Query<
(
Entity,
&BlueprintName,
Option<&Transform>,
Option<&Parent>,
Option<&Library>,
Option<&AddToGameWorld>,
Option<&Name>,
),
(
With<BlueprintAssetsLoaded>,
Added<BlueprintAssetsLoaded>,
Without<BlueprintAssetsNotLoaded>,
),
>,
mut commands: Commands,
mut game_world: Query<Entity, With<GameWorldTag>>,
assets_gltf: Res<Assets<Gltf>>,
mut graphs: ResMut<Assets<AnimationGraph>>,
asset_server: Res<AssetServer>,
blueprints_config: Res<BluePrintsConfig>,
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, &blueprints_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(|| &blueprints_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<Gltf> = 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<Entity> = vec![];
if let Ok(c) = children.get(entity) {
for child in c.iter() {
original_children.push(*child);
}
}
let mut graph = AnimationGraph::new();
let mut named_animations: HashMap<String, Handle<AnimationClip>> = HashMap::new();
let mut named_indices: HashMap<String, AnimationNodeIndex> = HashMap::new();
for (key, clip) in gltf.named_animations.iter() {
named_animations.insert(key.to_string(), clip.clone());
let animation_index = graph.add_clip(clip.clone(), 1.0, graph.root);
named_indices.insert(key.to_string(), animation_index);
}
let graph = graphs.add(graph);
commands.entity(entity).insert((
SceneBundle {
scene: scene.clone(),
transform: transforms,
..Default::default()
},
Animations {
named_animations,
named_indices,
graph,
},
Spawned,
OriginalChildren(original_children),
));
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);
}
}
}

View File

@ -1,100 +0,0 @@
use std::any::TypeId;
use bevy::gltf::Gltf;
use bevy::prelude::*;
use bevy::scene::SceneInstance;
// use bevy::utils::hashbrown::HashSet;
use super::{AnimationPlayerLink, Animations};
use super::{SpawnHere, Spawned};
use crate::{
AssetsToLoad, BlueprintAssetsLoaded, 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(
unprocessed_entities: Query<
(
Entity,
&Children,
&OriginalChildren,
&Animations,
Option<&NoInBlueprint>,
Option<&Name>,
),
(With<SpawnHere>, With<SceneInstance>, With<Spawned>),
>,
added_animation_players: Query<(Entity, &Parent), Added<AnimationPlayer>>,
all_children: Query<&Children>,
mut commands: Commands,
) {
for (original, children, original_children, animations, no_inblueprint, name) in
unprocessed_entities.iter()
{
debug!("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);
}
}
// copy components into from blueprint instance's root_entity to original entity
commands.add(CopyComponents {
source: root_entity,
destination: original,
exclude: vec![TypeId::of::<Parent>(), TypeId::of::<Children>()],
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(AnimationPlayerLink(added));
}
}
}
commands.entity(original).remove::<SpawnHere>();
commands.entity(original).remove::<Spawned>();
commands.entity(original).remove::<Handle<Scene>>();
commands.entity(original).remove::<AssetsToLoad<Gltf>>(); // 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::<BlueprintAssetsLoaded>();
commands.entity(root_entity).despawn_recursive();
}
}

View File

@ -1,4 +0,0 @@
This crate is available under either:
* The [MIT License](./LICENSE_MIT)
* The [Apache License, Version 2.0](./LICENSE_APACHE)

View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2023] [Mark "kaosat-dev" Moissette]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Mark "kaosat-dev" Moissette
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,97 +0,0 @@
use bevy::pbr::DirectionalLightShadowMap;
use bevy::prelude::*;
use crate::GltfComponentsSet;
pub(crate) fn plugin(app: &mut App) {
app.register_type::<BlenderBackgroundShader>()
.register_type::<BlenderShadowSettings>()
.register_type::<BlenderLightShadows>()
.add_systems(
Update,
(process_lights, process_shadowmap, process_background_shader)
.after(GltfComponentsSet::Injection),
);
}
#[derive(Component, Reflect, Default, Debug, PartialEq, Clone)]
#[reflect(Component)]
#[non_exhaustive]
/// The properties of a light's shadow , to enable controlling per light shadows from Blender
pub struct BlenderLightShadows {
pub enabled: bool,
pub buffer_bias: f32,
}
/// The background color as described by Blender's [background shader](https://docs.blender.org/manual/en/latest/render/shader_nodes/shader/background.html).
#[derive(Component, Reflect, Default, Debug, PartialEq, Clone)]
#[reflect(Component)]
#[non_exhaustive]
pub struct BlenderBackgroundShader {
pub color: Color,
pub strength: f32,
}
/// The settings used by EEVEE's [shadow rendering](https://docs.blender.org/manual/en/latest/render/eevee/render_settings/shadows.html).
#[derive(Component, Reflect, Default, Debug, PartialEq, Clone)]
#[reflect(Component)]
#[non_exhaustive]
pub struct BlenderShadowSettings {
pub cascade_size: usize,
}
fn process_lights(
mut directional_lights: Query<
(&mut DirectionalLight, Option<&BlenderLightShadows>),
Added<DirectionalLight>,
>,
mut spot_lights: Query<(&mut SpotLight, Option<&BlenderLightShadows>), Added<SpotLight>>,
mut point_lights: Query<(&mut PointLight, Option<&BlenderLightShadows>), Added<PointLight>>,
) {
for (mut light, blender_light_shadows) in directional_lights.iter_mut() {
if let Some(blender_light_shadows) = blender_light_shadows {
light.shadows_enabled = blender_light_shadows.enabled;
} else {
light.shadows_enabled = true;
}
}
for (mut light, blender_light_shadows) in spot_lights.iter_mut() {
if let Some(blender_light_shadows) = blender_light_shadows {
light.shadows_enabled = blender_light_shadows.enabled;
} else {
light.shadows_enabled = true;
}
}
for (mut light, blender_light_shadows) in point_lights.iter_mut() {
if let Some(blender_light_shadows) = blender_light_shadows {
light.shadows_enabled = blender_light_shadows.enabled;
} else {
light.shadows_enabled = true;
}
}
}
fn process_shadowmap(
shadowmaps: Query<&BlenderShadowSettings, Added<BlenderShadowSettings>>,
mut commands: Commands,
) {
for shadowmap in shadowmaps.iter() {
commands.insert_resource(DirectionalLightShadowMap {
size: shadowmap.cascade_size,
});
}
}
fn process_background_shader(
background_shaders: Query<&BlenderBackgroundShader, Added<BlenderBackgroundShader>>,
mut commands: Commands,
) {
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,
});
}
}

View File

@ -1,97 +0,0 @@
use bevy::{
core::Name,
ecs::{
entity::Entity,
query::{Added, Without},
reflect::{AppTypeRegistry, ReflectComponent},
world::World,
},
gltf::GltfExtras,
hierarchy::Parent,
log::debug,
reflect::{Reflect, TypeRegistration},
utils::HashMap,
};
use crate::{ronstring_to_reflect_component, GltfComponentsConfig, GltfProcessed};
/// main function: injects components into each entity in gltf files that have `gltf_extras`, using reflection
pub fn add_components_from_gltf_extras(world: &mut World) {
let mut extras =
world.query_filtered::<(Entity, &Name, &GltfExtras, &Parent), (Added<GltfExtras>, Without<GltfProcessed>)>();
let mut entity_components: HashMap<Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>> =
HashMap::new();
let gltf_components_config = world.resource::<GltfComponentsConfig>();
for (entity, name, extra, parent) in extras.iter(world) {
debug!(
"Name: {}, entity {:?}, parent: {:?}, extras {:?}",
name, entity, parent, extra
);
let type_registry: &AppTypeRegistry = world.resource();
let type_registry = type_registry.read();
let reflect_components = ronstring_to_reflect_component(
&extra.value,
&type_registry,
gltf_components_config.legacy_mode,
);
// we assign the components specified /xxx_components objects to their parent node
let mut target_entity = entity;
// if the node contains "components" or ends with "_pa" (ie add to parent), the components will not be added to the entity itself but to its parent
// this is mostly used for Blender collections
if name.as_str().contains("components") || name.as_str().ends_with("_pa") {
debug!("adding components to parent");
target_entity = parent.get();
}
debug!("adding to {:?}", target_entity);
// if there where already components set to be added to this entity (for example when entity_data was refering to a parent), update the vec of entity_components accordingly
// this allows for example blender collection to provide basic ecs data & the instances to override/ define their own values
if entity_components.contains_key(&target_entity) {
let mut updated_components: Vec<(Box<dyn Reflect>, TypeRegistration)> = Vec::new();
let current_components = &entity_components[&target_entity];
// first inject the current components
for (component, type_registration) in current_components {
updated_components.push((component.clone_value(), type_registration.clone()));
}
// then inject the new components: this also enables overwrite components set in the collection
for (component, type_registration) in reflect_components {
updated_components.push((component.clone_value(), type_registration));
}
entity_components.insert(target_entity, updated_components);
} else {
entity_components.insert(target_entity, reflect_components);
}
}
for (entity, components) in entity_components {
let type_registry: &AppTypeRegistry = world.resource();
let type_registry = type_registry.clone();
let type_registry = type_registry.read();
if !components.is_empty() {
debug!("--entity {:?}, components {}", entity, components.len());
}
for (component, type_registration) in components {
debug!(
"------adding {} {:?}",
component.get_represented_type_info().unwrap().type_path(),
component
);
{
let mut entity_mut = world.entity_mut(entity);
type_registration
.data::<ReflectComponent>()
.expect("Unable to reflect component")
.insert(&mut entity_mut, &*component, &type_registry);
entity_mut.insert(GltfProcessed); // this is how can we insert any additional components
}
}
}
}

View File

@ -1,134 +0,0 @@
use bevy::log::{debug, warn};
use bevy::reflect::serde::ReflectDeserializer;
use bevy::reflect::{Reflect, TypeInfo, TypeRegistration, TypeRegistry};
use bevy::utils::HashMap;
use ron::Value;
use serde::de::DeserializeSeed;
use super::capitalize_first_letter;
pub fn ronstring_to_reflect_component(
ron_string: &str,
type_registry: &TypeRegistry,
simplified_types: bool,
) -> Vec<(Box<dyn Reflect>, TypeRegistration)> {
let lookup: HashMap<String, Value> = ron::from_str(ron_string).unwrap();
let mut components: Vec<(Box<dyn Reflect>, TypeRegistration)> = Vec::new();
for (key, value) in lookup.into_iter() {
let type_string = key.replace("component: ", "").trim().to_string();
let capitalized_type_name = capitalize_first_letter(type_string.as_str());
let mut parsed_value: String;
match value.clone() {
Value::String(str) => {
parsed_value = str;
}
_ => parsed_value = ron::to_string(&value).unwrap().to_string(),
}
if let Some(type_registration) =
type_registry.get_with_short_type_path(capitalized_type_name.as_str())
{
debug!("TYPE INFO {:?}", type_registration.type_info());
if simplified_types {
if let TypeInfo::TupleStruct(info) = type_registration.type_info() {
// we handle tupple strucs with only one field differently, as Blender's custom properties with custom ui (float, int, bool, etc) always give us a tupple struct
if info.field_len() == 1 {
let field = info
.field_at(0)
.expect("we should always have at least one field here");
let field_name = field.type_path();
let mut formated = parsed_value.clone();
match field_name {
"f32" => {
formated = parsed_value.parse::<f32>().unwrap().to_string();
}
"f64" => {
formated = parsed_value.parse::<f64>().unwrap().to_string();
}
"u8" => {
formated = parsed_value.parse::<u8>().unwrap().to_string();
}
"u16" => {
formated = parsed_value.parse::<u16>().unwrap().to_string();
}
"u32" => {
formated = parsed_value.parse::<u32>().unwrap().to_string();
}
"u64" => {
formated = parsed_value.parse::<u64>().unwrap().to_string();
}
"u128" => {
formated = parsed_value.parse::<u128>().unwrap().to_string();
}
"glam::Vec2" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
formated = format!("(x:{},y:{})", parsed[0], parsed[1]);
}
"glam::Vec3" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
formated =
format!("(x:{},y:{},z:{})", parsed[0], parsed[1], parsed[2]);
}
"bevy_render::color::Color" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
if parsed.len() == 3 {
formated = format!(
"Rgba(red:{},green:{},blue:{}, alpha: 1.0)",
parsed[0], parsed[1], parsed[2]
);
}
if parsed.len() == 4 {
formated = format!(
"Rgba(red:{},green:{},blue:{}, alpha:{})",
parsed[0], parsed[1], parsed[2], parsed[3]
);
}
}
_ => {}
}
parsed_value = format!("({formated})");
}
}
if parsed_value.is_empty() {
parsed_value = "()".to_string();
}
}
let ron_string = format!(
"{{ \"{}\":{} }}",
type_registration.type_info().type_path(),
parsed_value
);
// usefull to determine what an entity looks like Serialized
/*let test_struct = CameraRenderGraph::new("name");
let serializer = ReflectSerializer::new(&test_struct, &type_registry);
let serialized =
ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();
println!("serialized Component {}", serialized);*/
debug!("component data ron string {}", ron_string);
let mut deserializer = ron::Deserializer::from_str(ron_string.as_str())
.expect("deserialzer should have been generated from string");
let reflect_deserializer = ReflectDeserializer::new(type_registry);
let component = reflect_deserializer
.deserialize(&mut deserializer)
.unwrap_or_else(|_| {
panic!(
"failed to deserialize component {} with value: {:?}",
key, value
)
});
debug!("component {:?}", component);
debug!("real type {:?}", component.get_represented_type_info());
components.push((component, type_registration.clone()));
debug!("found type registration for {}", capitalized_type_name);
} else {
warn!("no type registration for {}", capitalized_type_name);
}
}
components
}

View File

@ -1,21 +0,0 @@
[package]
name = "bevy_gltf_save_load"
version = "0.5.0"
authors = ["Mark 'kaosat-dev' Moissette"]
description = "Save & load your bevy games"
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
keywords = ["gamedev", "bevy", "save", "load", "serialize"]
categories = ["game-development"]
edition = "2021"
license = "MIT OR Apache-2.0"
[lints]
workspace = true
[dependencies]
bevy = { version = "0.14", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] }
bevy_gltf_blueprints = { version = "0.11", path = "../bevy_gltf_blueprints" }
[dev-dependencies]
bevy = { version = "0.14", default-features = false, features = ["dynamic_linking"] }

View File

@ -1,4 +0,0 @@
This crate is available under either:
* The [MIT License](./LICENSE_MIT)
* The [Apache License, Version 2.0](./LICENSE_APACHE)

View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2023] [Mark "kaosat-dev" Moissette]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Mark "kaosat-dev" Moissette
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,216 +0,0 @@
use gltf_json as json;
use json::camera::Type;
use json::validation::{Checked, Validate};
use serde_json::value::{to_raw_value, RawValue};
use serde::Serialize;
use bevy::reflect::TypeRegistryArc;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum Output {
/// Output standard glTF.
Standard,
/// Output binary glTF.
Binary,
}
#[derive(Serialize)]
struct MyExtraData {
a: u32,
b: u32,
BlueprintName: String,
SpawnHere: String,
}
/*
pub fn serialize_gltf_inner<S>(serialize: S) -> Result<String, json::Error>
where
S: Serialize,
{
let pretty_config = ron::ser::PrettyConfig::default()
.indentor(" ".to_string())
.new_line("\n".to_string());
ron::ser::to_string_pretty(&serialize, pretty_config)
}*/
pub fn serialize_gltf(scene:&DynamicScene, registry: &TypeRegistryArc) {
}
pub fn save_game(
world: &mut World,
) {
let mut save_path:String = "".into();
let mut events = world
.resource_mut::<Events<SaveRequest>>();
for event in events.get_reader().read(&events) {
info!("SAVE EVENT !! {:?}", event);
save_path = event.path.clone();
}
info!("SAVING TO {}", save_path);
events.clear();
let saveable_entities: Vec<Entity> = world
.query_filtered::<Entity, With<Dynamic>>()
.iter(world)
.collect();
debug!("saveable entities {}", saveable_entities.len());
let components = HashSet::from([
TypeId::of::<Name>(),
TypeId::of::<Transform>(),
TypeId::of::<Velocity>() ,
TypeId::of::<BlueprintName>(),
TypeId::of::<SpawnHere>(),
TypeId::of::<Dynamic>(),
TypeId::of::<Camera>(),
TypeId::of::<Camera3d>(),
TypeId::of::<Tonemapping>(),
TypeId::of::<CameraTrackingOffset>(),
TypeId::of::<Projection>(),
TypeId::of::<CameraRenderGraph>(),
TypeId::of::<Frustum>(),
TypeId::of::<GlobalTransform>(),
TypeId::of::<VisibleEntities>(),
TypeId::of::<Pickable>(),
]);
let filter = SceneFilter::Allowlist(components);
let mut scene_builder = DynamicSceneBuilder::from_world(world).with_filter(filter);
let dyn_scene = scene_builder
/* .allow::<Transform>()
.allow::<Velocity>()
.allow::<BlueprintName>()*/
/* .deny::<Children>()
.deny::<Parent>()
.deny::<InheritedVisibility>()
.deny::<Visibility>()
.deny::<GltfExtras>()
.deny::<GlobalTransform>()
.deny::<Collider>()
.deny::<RigidBody>()
.deny::<Saveable>()
// camera stuff
.deny::<Camera>()
.deny::<CameraRenderGraph>()
.deny::<Camera3d>()
.deny::<Clusters>()
.deny::<VisibleEntities>()
.deny::<VisiblePointLights>()
//.deny::<HasGizmoMarker>()
*/
.extract_entities(saveable_entities.into_iter())
.build();
let serialized_scene = dyn_scene
.serialize_ron(world.resource::<AppTypeRegistry>())
.unwrap();
let mut root = gltf_json::Root::default();
// unfortunatly, not available yet
/*let node = root.push(json::Node {
//mesh: Some(mesh),
..Default::default()
});
root.push(json::Scene {
extensions: Default::default(),
extras: Default::default(),
name: None,
nodes: vec![node],
});*/
let camera = json::camera::Perspective{
aspect_ratio: Some(0.5),
yfov: 32.0,
zfar: Some(30.),
znear: 0.0,
extensions: None,
extras: None
};
/*let camera = json::Camera{
name:Some("Camera".into()),
orthographic: None,
perspective:None,
extensions: None,
extras: None,
type_: Checked<Type::Perspective>,
};*/
let gna = to_raw_value(&MyExtraData { a: 1, b: 2, BlueprintName: "Foo".into(), SpawnHere:"".into() }).unwrap() ;
let node = json::Node {
camera: None,//Some(camera),
children: None,
extensions: None,
extras: Some(gna),
matrix: None,
mesh:None,
name: Some("yeah".into()),
rotation: None,
scale: None,
translation: Some([0.5, 10.0 ,-100.]),
skin: None,
weights: None
// mesh: Some(json::Index::new(0)),
//..Default::default()
};
let root = json::Root {
accessors: vec![], //[positions, colors],
buffers: vec![],
buffer_views: vec![],
meshes: vec![],
nodes: vec![node],
scenes: vec![json::Scene {
extensions: Default::default(),
extras: Default::default(),
name: Some("Foo".to_string()),
nodes: vec![json::Index::new(0)],
}],
..Default::default()
};
let gltf_save_name = "test.gltf";
let writer = fs::File::create(format!("assets/scenes/{gltf_save_name}") ).expect("I/O error");
json::serialize::to_writer_pretty(writer, &root).expect("Serialization error");
// let bin = to_padded_byte_vector(triangle_vertices);
// let mut writer = fs::File::create("triangle/buffer0.bin").expect("I/O error");
// writer.write_all(&bin).expect("I/O error");
#[cfg(not(target_arch = "wasm32"))]
IoTaskPool::get()
.spawn(async move {
// Write the scene RON data to file
File::create(format!("assets/scenes/{save_path}"))
.and_then(|mut file| file.write(serialized_scene.as_bytes()))
.expect("Error while writing scene to file");
})
.detach();
}

View File

@ -1,21 +0,0 @@
[package]
name = "bevy_registry_export"
version = "0.4.0"
authors = ["Mark 'kaosat-dev' Moissette", "Pascal 'Killercup' Hertleif"]
description = "Allows you to create a Json export of all your components/ registered types of your Bevy app/game"
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
keywords = ["gamedev", "bevy", "assets", "registry", "components"]
categories = ["game-development"]
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy = { version = "0.14", default-features = false, features = ["bevy_scene"] }
bevy_reflect = { version = "0.14", default-features = false }
bevy_app = { version = "0.14", default-features = false, features = ["bevy_reflect"] }
bevy_ecs = { version = "0.14", default-features = false, features = ["bevy_reflect"] }
serde_json = "1.0.108"
[dev-dependencies]
bevy = { version = "0.14", default-features = false, features = ["dynamic_linking"] }

View File

@ -1,4 +0,0 @@
This crate is available under either:
* The [MIT License](./LICENSE_MIT)
* The [Apache License, Version 2.0](./LICENSE_APACHE)

View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2024] [Mark "kaosat-dev" Moissette]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,10 +1,10 @@
[package]
name = "bevy_gltf_components"
version = "0.6.0"
name = "blenvy"
version = "0.1.0-alpha.1"
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"
@ -14,9 +14,24 @@ license = "MIT OR Apache-2.0"
workspace = true
[dependencies]
bevy = { version = "0.14", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] }
bevy = { version = "0.14", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "file_watcher"] }
serde = "1.0.188"
ron = "0.8.1"
serde_json = "1.0.108"
gltf = { version = "1.4.0", default-features = false, features = [
"KHR_lights_punctual",
"KHR_materials_transmission",
"KHR_materials_ior",
"KHR_materials_volume",
"KHR_materials_unlit",
"KHR_materials_emissive_strength",
"KHR_texture_transform",
"extras",
"extensions",
"names",
"utils",
] }
[dev-dependencies]
bevy = { version = "0.14", default-features = false, features = ["dynamic_linking"] }

331
crates/blenvy/README.md Normal file
View File

@ -0,0 +1,331 @@
[![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/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 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 !
A blueprint is a set of **overrideable** components + a hierarchy: ie
* 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/Blenvy/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/) .
* 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/Blenvy/tree/main/tools/blenvy) that does a lot of the work for you (including spliting generating seperate gltf files for your static vs dynamic assets)
## Usage
Here's a minimal usage example:
```toml
# Cargo.toml
[dependencies]
bevy="0.14"
blenvy = { version = "0.1.0"}
```
```rust no_run
use bevy::prelude::*;
use blenvy::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(BlenvyPlugin)
.add_systems(Startup, setup_game)
.add_systems(Update, spawn_blueprint_instance)
.run();
}
// 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<Input<KeyCode>>,
){
if keycode.just_pressed(KeyCode::S) {
let new_entity = commands.spawn((
BlueprintInfo(name: "Health_Pickup".to_string(), path:""), // mandatory !!
SpawnBlueprint, // mandatory !!
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), // VERY important !!
// any other component you want to insert
));
}
}
```
## Installation
Add the following to your `[dependencies]` section in `Cargo.toml`:
```toml
blenvy = "0.1.0"
```
Or use `cargo add`:
```toml
cargo add blenvy
```
## Setup
```rust no_run
use bevy::prelude::*;
use blenvy::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(BlenvyPlugin)
.run();
}
```
you may want to configure your "library"/"blueprints" settings:
```rust no_run
use bevy::prelude::*;
use blenvy::*;
fn main() {
App::new()
.add_plugins((
BlenvyPlugin{
aabbs: true, // defaults to false, enable this to automatically calculate aabb for the scene/blueprint
..Default::default()
}
))
.run();
}
```
## Spawning entities from blueprints
You can spawn entities from blueprints like this:
```rust no_run
commands.spawn((
BlueprintInfo("Health_Pickup".to_string()), // mandatory !!
SpawnBlueprint, // mandatory !!
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), // optional
// any other component you want to insert
))
```
Once spawning of the actual entity is done, the spawned Blueprint will be *gone/merged* with the contents of Blueprint !
> Important :
you can **add** or **override** components present inside your Blueprint when spawning the BluePrint itself: ie
### Adding components not specified inside the blueprint
you can just add any additional components you need when spawning :
```rust no_run
commands.spawn((
BlueprintInfo("Health_Pickup".to_string()),
SpawnBlueprint,
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
// from Rapier/bevy_xpbd: this means the entity will also have a velocity component when inserted into the world
Velocity {
linvel: Vec3::new(vel_x, vel_y, vel_z),
angvel: Vec3::new(0.0, 0.0, 0.0),
},
))
```
### Overriding components specified inside the blueprint
any component you specify when spawning the Blueprint that is also specified **within** the Blueprint will **override** that component in the final spawned entity
for example
```rust no_run
commands.spawn((
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
))
```
### BluePrintBundle
There is also a ```BluePrintBundle``` for convenience , which just has
* a ```BlueprintInfo``` component
* a ```SpawnBlueprint``` component
## Additional information
- When a blueprint is spawned, an ```FromBlueprint``` 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"
This way all your levels, your dynamic entities etc, are kept seperated from UI nodes & other entities that are not relevant to the game world
> Note: you should only have a SINGLE entity tagged with that component !
```rust no_run
commands.spawn((
BlueprintInfo::from_path("levels/World.glb"),
SpawnBlueprint,
HideUntilReady,
GameWorldTag, // here it is
));
```
## SystemSet
the ordering of systems is very important !
For example to replace your proxy components (stand-in components when you cannot/ do not want to use real components in the gltf file) with actual ones, which should happen **AFTER** the Blueprint based spawning,
so ```blenvy``` provides a **SystemSet** for that purpose: ```GltfBlueprintsSet```
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/Blenvy/tree/main/examples/blenvy/basic for how to set it up correctly
## Animation
```blenvy``` provides some lightweight helpers to deal with animations stored in gltf files
* an ```Animations``` component that gets inserted into spawned (root) entities that contains a hashmap of all animations contained inside that entity/gltf file .
(this is a copy of the ```named_animations``` inside Bevy's gltf structures )
* an ```AnimationPlayerLink``` component that gets inserted into spawned (root) entities, to make it easier to trigger/ control animations than it usually is inside Bevy + Gltf files
The workflow for animations is as follows:
* create a gltf file with animations (using Blender & co) as you would normally do
* inside Bevy, use the ```blenvy``` boilerplate (see sections above), no specific setup beyond that is required
* to control the animation of an entity, you need to query for entities that have both ```AnimationPlayerLink``` and ```Animations``` components (added by ```blenvy```) AND entities with the ```AnimationPlayer``` component
For example:
```rust no_run
// example of changing animation of entities based on proximity to the player, for "fox" entities (Tag component)
pub fn animation_change_on_proximity_foxes(
players: Query<&GlobalTransform, With<Player>>,
animated_foxes: Query<(&GlobalTransform, &AnimationPlayerLink, &Animations ), With<Fox>>,
mut animation_players: Query<&mut AnimationPlayer>,
){
for player_transforms in players.iter() {
for (fox_tranforms, link, animations) in animated_foxes.iter() {
let distance = player_transforms
.translation()
.distance(fox_tranforms.translation());
let mut anim_name = "Walk";
if distance < 8.5 {
anim_name = "Run";
}
else if distance >= 8.5 && distance < 10.0{
anim_name = "Walk";
}
else if distance >= 10.0 && distance < 15.0{
anim_name = "Survey";
}
// now play the animation based on the chosen animation name
let mut animation_player = animation_players.get_mut(link.0).unwrap();
animation_player.play_with_transition(
animations.named_animations.get(anim_name).expect("animation name should be in the list").clone(),
Duration::from_secs(3)
).repeat();
}
}
}
```
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/Blenvy/tree/main/examples/blenvy/animation/game/in_game.rs
## Materials
Ff you enable it on the blender side, Blenvy will be using "material libraries" to share common textures/materials between blueprints, in order to avoid asset & memory bloat:
Ie for example without this option, 56 different blueprints using the same material with a large texture would lead to the material/texture being embeded
56 times !!
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/blenvy)
## Examples
https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/components
https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/blueprints
https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/animation
https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/save_load
https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/demo (a full fledged demo)
## Compatible Bevy versions
The main branch is compatible with the latest Bevy release, while the branch `bevy_main` tries to track the `main` branch of Bevy (PRs updating the tracked commit are welcome).
Compatibility of `blenvy` versions:
| `blenvy` | `bevy` |
| :-- | :-- |
| `0.1 - 0.2` | `0.14` |
| branch `main` | `0.14` |
| branch `bevy_main` | `main` |
## License
This crate, all its code, contents & assets is Dual-licensed under either of
- Apache License, Version 2.0, ([LICENSE-APACHE](./LICENSE_APACHE.md) or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](./LICENSE_MIT.md) or https://opensource.org/licenses/MIT)

View File

@ -1,14 +1,15 @@
use bevy::{math::Vec3A, prelude::*, render::primitives::Aabb};
use crate::{BluePrintsConfig, Spawned};
use crate::{BlenvyConfig, BlueprintReadyForFinalizing, BlueprintReadyForPostProcess};
/// helper system that computes the compound aabbs of the scenes/blueprints
pub fn compute_scene_aabbs(
root_entities: Query<(Entity, &Name), (With<Spawned>, Without<Aabb>)>,
root_entities: Query<(Entity, &Name), (With<BlueprintReadyForPostProcess>, Without<Aabb>)>,
other_entities: Query<Entity, (With<BlueprintReadyForPostProcess>, With<Aabb>)>,
children: Query<&Children>,
existing_aabbs: Query<&Aabb>,
mut blueprints_config: ResMut<BluePrintsConfig>,
mut blenvy_config: ResMut<BlenvyConfig>,
mut commands: Commands,
) {
// compute compound aabb
@ -16,18 +17,29 @@ pub fn compute_scene_aabbs(
// info!("generating aabb for {:?}", name);
// only recompute aabb if it has not already been done before
if blueprints_config.aabb_cache.contains_key(&name.to_string()) {
let aabb = blueprints_config
if blenvy_config.aabb_cache.contains_key(&name.to_string()) {
let aabb = blenvy_config
.aabb_cache
.get(&name.to_string())
.expect("we should have the aabb available");
commands.entity(root_entity).insert(*aabb);
commands
.entity(root_entity)
.insert(*aabb)
.insert(BlueprintReadyForFinalizing);
} else {
let aabb = compute_descendant_aabb(root_entity, &children, &existing_aabbs);
commands.entity(root_entity).insert(aabb);
blueprints_config.aabb_cache.insert(name.to_string(), aabb);
blenvy_config.aabb_cache.insert(name.to_string(), aabb);
info!("generating aabb for {:?}", name);
commands
.entity(root_entity)
.insert(aabb)
.insert(BlueprintReadyForFinalizing);
}
}
for entity in other_entities.iter() {
println!("already got AABB");
commands.entity(entity).insert(BlueprintReadyForFinalizing); // FIXME ! Yikes !!
}
}
pub fn compute_descendant_aabb(

View File

@ -0,0 +1,243 @@
use bevy::prelude::*;
use bevy::utils::HashMap;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// 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>>,
pub named_indices: HashMap<String, AnimationNodeIndex>,
pub graph: Handle<AnimationGraph>,
}
#[derive(Component, Debug)]
/// Stop gap helper component : this is inserted into a "root" entity (an entity representing a whole gltf file)
/// so that the root entity knows which of its children contains an actualy `AnimationPlayer` component
/// this is for convenience, because currently , Bevy's gltf parsing inserts `AnimationPlayers` "one level down"
/// 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, Debug)]
/// Same as the above but for `AnimationInfos` components which get added (on the Blender side) to the entities that actually have the animations
/// which often is not the Blueprint or blueprint instance entity itself.
pub struct BlueprintAnimationInfosLink(pub Entity);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// storage for scene level animations for a given entity (hierarchy), essentially a clone of gltf's `named_animations`
pub struct SceneAnimations {
pub named_animations: HashMap<String, Handle<AnimationClip>>,
pub named_indices: HashMap<String, AnimationNodeIndex>,
pub graph: Handle<AnimationGraph>,
}
#[derive(Component, Debug)]
/// Stop gap helper component : this is inserted into a "root" entity (an entity representing a whole gltf file)
/// so that the root entity knows which of its children contains an actualy `AnimationPlayer` component
/// this is for convenience, because currently , Bevy's gltf parsing inserts `AnimationPlayers` "one level down"
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
pub struct SceneAnimationPlayerLink(pub Entity);
#[derive(Component, Debug)]
/// Same as the above but for scene's `AnimationInfos` components which get added (on the Blender side) to the entities that actually have the animations
/// which often is not the Blueprint or blueprint instance entity itself.
pub struct SceneAnimationInfosLink(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 the `blenvy` Blender add-on on entities that have animations
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct AnimationInfos {
pub animations: Vec<AnimationInfo>,
}
#[derive(Reflect, Default, Debug)]
pub struct AnimationMarker {
// pub frame: u32,
pub name: String,
pub handled_for_cycle: bool,
}
/// 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>>>);
/// 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,
}
/////////////////////
/// triggers events when a given animation marker is reached for BLUEPRINT animations
pub fn trigger_blueprint_animation_markers_events(
animation_data: Query<(Entity, &BlueprintAnimationPlayerLink, &BlueprintAnimationInfosLink, &BlueprintAnimations)>,
// FIXME: annoying hiearchy issue yet again: the Markers & AnimationInfos are stored INSIDE the blueprint, so we need to access them differently
animation_infos: Query<(&AnimationInfos, &AnimationMarkers)>,
animation_players: Query<&AnimationPlayer>,
mut animation_marker_events: EventWriter<AnimationMarkerReached>,
animation_clips: Res<Assets<AnimationClip>>,
) {
for (entity, player_link, infos_link, animations) in animation_data.iter() {
for (animation_name, node_index) in animations.named_indices.iter() {
let animation_player = animation_players.get(player_link.0).unwrap();
let (animation_infos, animation_markers) = animation_infos.get(infos_link.0).unwrap();
if animation_player.animation_is_playing(*node_index) {
if let Some(animation) = animation_player.animation(*node_index) {
// animation.speed()
// animation.completions()
if let Some(animation_clip_handle) = animations.named_animations.get(animation_name) {
if let Some(animation_clip) = animation_clips.get(animation_clip_handle) {
let animation_length_seconds = animation_clip.duration();
let animation_length_frames = animation_infos // FIXME: horribly inneficient
.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.elapsed()
- (animation.completions() as f32) * animation_length_seconds;
let frame_seconds = (animation_length_frames / animation_length_seconds)
* time_in_animation;
// println!("frame seconds {}", frame_seconds);
let frame = frame_seconds.ceil() as u32; // FIXME , bad hack
let matching_animation_marker = &animation_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);
// FIXME: complete hack-ish solution , otherwise this can fire multiple times in a row, depending on animation length , speed , etc
let diff = frame as f32 - frame_seconds;
if diff < 0.1 {
for marker_name in matching_markers_per_frame {
animation_marker_events.send(AnimationMarkerReached {
entity,
animation_name: animation_name.clone(),
frame,
marker_name: marker_name.clone(),
});
}
}
}
}
}
}
}
}
}
}
/// triggers events when a given animation marker is reached for INSTANCE animations
pub fn trigger_instance_animation_markers_events(
animation_infos: Query<(
Entity,
&AnimationMarkers,
&SceneAnimationPlayerLink,
&SceneAnimations,
&AnimationInfos,
)>,
animation_players: Query<&AnimationPlayer>,
animation_clips: Res<Assets<AnimationClip>>,
animation_graphs: Res<Assets<AnimationGraph>>,
mut animation_marker_events: EventWriter<AnimationMarkerReached>,
) {
for (entity, markers, player_link, animations, animation_infos) in animation_infos.iter() {
//let (animation_player, animation_transitions) = animation_players.get(player_link.0).unwrap();
//let foo = animation_transitions.get_main_animation().unwrap();
for (animation_name, node_index) in animations.named_indices.iter() {
let animation_player = animation_players.get(player_link.0).unwrap();
if animation_player.animation_is_playing(*node_index) {
if let Some(animation) = animation_player.animation(*node_index) {
if let Some(animation_clip_handle) = animations.named_animations.get(animation_name) {
if let Some(animation_clip) = animation_clips.get(animation_clip_handle) {
println!("helooo")
}
}
}
}
}
/*let animation_clip = animation_clips.get(animation_player.animation_clip());
// animation_player.play(animation)
if animation_clip.is_some() {
// 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 / 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();
// let timediff = animation_length_seconds - time_in_animation;
// println!("timediff {}", timediff);
// println!("FOUND A MARKER {:?} at frame {}", matching_markers_per_frame, frame);
// emit an event 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 in matching_markers_per_frame {
animation_marker_events.send(AnimationMarkerReached {
entity,
animation_name: animation_name.clone(),
frame,
marker_name: marker.clone(),
});
}
}
}
}*/
}
}

View File

@ -0,0 +1,77 @@
use bevy::{asset::LoadedUntypedAsset, prelude::*};
use serde::Deserialize;
/// helper component, is used to store the list of sub blueprints to enable automatic loading of dependend blueprints
#[derive(Component, Reflect, Default, Debug, Deserialize)]
#[reflect(Component)]
pub struct BlueprintAsset {
pub name: String,
pub path: String,
}
/// helper component, is used to store the list of sub blueprints to enable automatic loading of dependend blueprints
/// these are only the DIRECT dependencies of a blueprint, does not contain the indirect assets (ie assets of sub blueprints, etc)
#[derive(Component, Reflect, Default, Debug, Deserialize)]
#[reflect(Component)]
pub struct BlueprintAssets {
/// only this field should get filled in from the Blender side
pub assets: Vec<BlueprintAsset>,
/// set to default when deserializing
#[serde(default)]
#[reflect(default)]
pub loaded: bool,
/// set to default when deserializing
#[serde(default)]
#[reflect(default)]
pub progress: f32,
#[reflect(ignore)]
#[serde(skip)]
pub asset_infos: Vec<AssetLoadTracker>,
}
//(pub Vec<BlueprintAsset>);
/// helper component, is used to store the list of sub blueprints to enable automatic loading of dependend blueprints
#[derive(Component, Reflect, Default, Debug, Deserialize)]
pub struct BlueprintAllAssets {
/// only this field should get filled in from the Blender side
pub assets: Vec<BlueprintAsset>,
}
////////////////////////
///
/// flag component, usually added when a blueprint is loaded
#[derive(Component)]
pub(crate) struct BlueprintAssetsLoaded;
/// flag component
#[derive(Component)]
pub(crate) struct BlueprintAssetsNotLoaded;
/// helper component, for tracking loaded assets's loading state, id , handle etc
#[derive(Debug, Reflect)]
pub struct AssetLoadTracker {
#[allow(dead_code)]
pub name: String,
pub path: String,
pub id: AssetId<LoadedUntypedAsset>,
pub loaded: bool,
#[allow(dead_code)]
pub handle: Handle<LoadedUntypedAsset>,
}
/// helper component, for tracking loaded assets
#[derive(Component, Debug)]
pub(crate) struct BlueprintAssetsLoadState {
pub all_loaded: bool,
pub asset_infos: Vec<AssetLoadTracker>,
pub progress: f32,
}
impl Default for BlueprintAssetsLoadState {
fn default() -> Self {
Self {
all_loaded: Default::default(),
asset_infos: Default::default(),
progress: Default::default(),
}
}
}

View File

@ -1,4 +1,5 @@
use bevy::{ecs::world::Command, prelude::*};
use bevy::ecs::world::Command;
use bevy::prelude::*;
use std::any::TypeId;
// originally based https://github.com/bevyengine/bevy/issues/1515,

View File

@ -0,0 +1,107 @@
use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintInfo, BlueprintInstanceReady, BlueprintSpawning, FromBlueprint, SpawnBlueprint, SubBlueprintsSpawnTracker};
use bevy::asset::AssetEvent;
use bevy::prelude::*;
use bevy::scene::SceneInstance;
use bevy::utils::hashbrown::HashMap;
/// Resource mapping asset paths (ideally untyped ids, but more complex) to a list of blueprint instance entity ids
#[derive(Debug, Clone, Resource, Default)]
pub(crate) struct AssetToBlueprintInstancesMapper{
// pub(crate) untyped_id_to_blueprint_entity_ids: HashMap<UntypedAssetId, Vec<Entity>>
pub(crate) untyped_id_to_blueprint_entity_ids: HashMap<String, Vec<Entity>>
}
pub(crate) fn react_to_asset_changes(
mut gltf_events: EventReader<AssetEvent<Gltf>>, // FIXME: Problem: we need to react to any asset change, not just gltf files !
// mut untyped_events: EventReader<AssetEvent<LoadedUntypedAsset>>,
blueprint_assets: Query<(
Entity,
Option<&Name>,
&BlueprintInfo,
Option<&Children>,
)>,
blueprint_children_entities: Query<&FromBlueprint>, //=> can only be used if the entites are tagged
assets_to_blueprint_instances: Res<AssetToBlueprintInstancesMapper>,
all_parents: Query<&Parent>,
spawning_blueprints: Query<&BlueprintSpawning>,
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
let mut respawn_candidates: Vec<&Entity> = vec![];
for event in gltf_events.read() {
// LoadedUntypedAsset
match event {
AssetEvent::Modified { id } => {
// React to the gltf file being modified
// println!("Modified gltf {:?}", asset_server.get_path(*id));
if let Some(asset_path) = asset_server.get_path(*id) {
// let untyped = asset_server.get_handle_untyped(asset_path.clone());
// println!("matching untyped handle {:?}", untyped);
// let bla = untyped.unwrap().id();
// asset_server.get
// in order to avoid respawn both a parent & a child , which would crash Bevy, we do things in two steps
if let Some(entities) = assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids.get(&asset_path.to_string()) {
for entity in entities.iter() {
println!("matching blueprint instance {}", entity);
// disregard entities that are already (re) spawning
if !respawn_candidates.contains(&entity) && blueprint_assets.get(*entity).is_ok() && spawning_blueprints.get(*entity).is_err()
{
respawn_candidates.push(entity);
}
}
}
}
}
_ => {}
}
}
// we process all candidates here to deal with the case where multiple assets have changed in a single frame, which could cause respawn chaos
// now find hierarchy of changes and only set the uppermost parent up for respawning
// TODO: improve this, very inneficient
let mut retained_candidates: Vec<Entity> = vec![];
'outer: for entity in respawn_candidates.iter() {
for parent in all_parents.iter_ancestors(**entity){
for ent in respawn_candidates.iter() {
if **ent == parent {
if ! retained_candidates.contains(&parent) {
retained_candidates.push(parent);
}
continue 'outer;
}
}
}
if ! retained_candidates.contains(entity) {
retained_candidates.push(**entity);
}
}
// println!("respawn candidates {:?}", respawn_candidates);
for retained in retained_candidates.iter() {
println!("retained {}", retained);
if let Ok((entity, entity_name, _blueprint_info, children)) = blueprint_assets.get(*retained) {
println!("HOLY MOLY IT DETECTS !!, now respawn {:?}", entity_name);
// TODO: only remove those that are "in blueprint"
if children.is_some() {
for child in children.unwrap().iter() {
commands.entity(*child).despawn_recursive();
}
}
commands
.entity(entity)
.remove::<BlueprintInstanceReady>()
.remove::<BlueprintAssetsLoaded>()
.remove::<SceneInstance>()
.remove::<BlueprintAssetsLoadState>()
.remove::<SubBlueprintsSpawnTracker>()
.insert(SpawnBlueprint);
}
}
// println!("done with asset updates");
}

View File

@ -0,0 +1,88 @@
use bevy::prelude::*;
use crate::BlenvyConfig;
#[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,
}
#[derive(Component, Default, Debug)]
pub struct MaterialProcessed;
/// system that injects / replaces materials from material library
pub(crate) fn inject_materials(
mut blenvy_config: ResMut<BlenvyConfig>,
material_infos: Query<
(Entity, &MaterialInfo, &Children),
Without<MaterialProcessed>, // (With<BlueprintReadyForPostProcess>)
/*(
Added<BlueprintMaterialAssetsLoaded>,
With<BlueprintMaterialAssetsLoaded>,
),*/
>,
with_materials_and_meshes: Query<
(),
(
With<Parent>,
With<Handle<StandardMaterial>>,
With<Handle<Mesh>>,
),
>,
assets_gltf: Res<Assets<Gltf>>,
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
for (entity, material_info, children) in material_infos.iter() {
let material_full_path = format!("{}#{}", material_info.path, material_info.name);
let mut material_found: Option<&Handle<StandardMaterial>> = 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<Gltf> = asset_server.load(material_info.path.clone()); // FIXME: kinda weird now
let mat_gltf = assets_gltf.get(model_handle.id()).unwrap_or_else(|| panic!("materials file {} should have been preloaded", material_info.path));
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 at this stage, please make sure you are correctly preloading them");
blenvy_config
.materials_cache
.insert(material_full_path, material.clone());
material_found = Some(material);
}
}
commands.entity(entity).insert(MaterialProcessed);
if let Some(material) = material_found {
for child in children.iter() {
if with_materials_and_meshes.contains(*child) {
info!(
"injecting material {}, path: {:?}",
material_info.name,
material_info.path.clone()
);
commands.entity(*child).insert(material.clone());
}
}
}
}
}

View File

@ -0,0 +1,151 @@
pub mod spawn_from_blueprints;
pub use spawn_from_blueprints::*;
pub mod animation;
pub use animation::*;
pub mod aabb;
pub use aabb::*;
pub mod assets;
pub use assets::*;
pub mod materials;
pub use materials::*;
pub mod copy_components;
pub use copy_components::*;
pub(crate) mod hot_reload;
pub(crate) use hot_reload::*;
use bevy::{prelude::*, utils::hashbrown::HashMap};
use crate::{BlenvyConfig, GltfComponentsSet};
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
/// set for the two stages of blueprint based spawning :
pub enum GltfBlueprintsSet {
Spawn,
AfterSpawn,
}
#[derive(Bundle)]
pub struct BluePrintBundle {
pub blueprint: BlueprintInfo,
pub spawn_here: SpawnBlueprint,
}
impl Default for BluePrintBundle {
fn default() -> Self {
BluePrintBundle {
blueprint: BlueprintInfo {
name: "default".into(),
path: "".into(),
},
spawn_here: SpawnBlueprint,
}
}
}
#[derive(Debug, Clone)]
/// Plugin for gltf blueprints
pub struct BlueprintsPlugin {
/// Automatically generate aabbs for the blueprints root objects
pub aabbs: bool,
}
impl Default for BlueprintsPlugin {
fn default() -> Self {
Self { aabbs: false }
}
}
fn aabbs_enabled(blenvy_config: Res<BlenvyConfig>) -> bool {
blenvy_config.aabbs
}
fn hot_reload(watching_for_changes: Res<WatchingForChanges>) -> bool {
// println!("hot reload ? {}", watching_for_changes.0);
watching_for_changes.0
}
trait BlenvyApp {
fn register_watching_for_changes(&mut self) -> &mut Self;
}
impl BlenvyApp for App {
fn register_watching_for_changes(&mut self) -> &mut Self {
let asset_server = self
.world()
.get_resource::<AssetServer>()
.expect(ASSET_ERROR);
let watching_for_changes = asset_server.watching_for_changes();
self.insert_resource(WatchingForChanges(watching_for_changes))
}
}
#[derive(Debug, Clone, Resource, Default)]
pub(crate) struct WatchingForChanges(pub(crate) bool);
const ASSET_ERROR: &str = ""; // TODO
impl Plugin for BlueprintsPlugin {
fn build(&self, app: &mut App) {
app.register_watching_for_changes()
.insert_resource(AssetToBlueprintInstancesMapper {
untyped_id_to_blueprint_entity_ids: HashMap::new(),
})
.add_event::<BlueprintEvent>()
.register_type::<BlueprintInfo>()
.register_type::<MaterialInfo>()
.register_type::<SpawnBlueprint>()
.register_type::<BlueprintAnimations>()
.register_type::<SceneAnimations>()
.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::<BlueprintAsset>()
.register_type::<Vec<BlueprintAsset>>()
.register_type::<Vec<String>>()
.register_type::<BlueprintAssets>()
.register_type::<HashMap<String, Vec<String>>>()
.register_type::<HideUntilReady>()
.configure_sets(
Update,
(GltfBlueprintsSet::Spawn, GltfBlueprintsSet::AfterSpawn)
.chain()
.after(GltfComponentsSet::Injection),
)
.add_systems(
Update,
(
blueprints_prepare_spawn,
blueprints_check_assets_loading,
blueprints_assets_loaded,
blueprints_scenes_spawned,
blueprints_cleanup_spawned_scene,
// post process
inject_materials,
compute_scene_aabbs, // .run_if(aabbs_enabled),
blueprints_finalize_instances,
)
.chain()
.in_set(GltfBlueprintsSet::Spawn),
)
// animation
.add_systems(
Update,
(
trigger_blueprint_animation_markers_events,
trigger_instance_animation_markers_events
),
)
// hot reload
.add_systems(Update, react_to_asset_changes.run_if(hot_reload));
}
}

View File

@ -0,0 +1,771 @@
use std::path::PathBuf;
use bevy::{gltf::Gltf, prelude::*, scene::SceneInstance, utils::hashbrown::HashMap};
use serde_json::Value;
use crate::{
AnimationInfos, AssetLoadTracker, AssetToBlueprintInstancesMapper, BlenvyConfig, BlueprintAnimationInfosLink, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintAssets, BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintAssetsNotLoaded, SceneAnimationInfosLink, SceneAnimationPlayerLink, SceneAnimations
};
/// this is a flag component for our levels/game world
#[derive(Component)]
pub struct GameWorldTag;
/// Main component for the blueprints
/// has both name & path of the blueprint to enable injecting the data from the correct blueprint
/// into the entity that contains this component
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct BlueprintInfo {
pub name: String,
pub path: String,
}
use std::path::Path;
impl BlueprintInfo {
pub fn from_path(path: &str) -> BlueprintInfo {
let p = Path::new(&path);
return BlueprintInfo {
name: p.file_stem().unwrap().to_os_string().into_string().unwrap(), // seriously ? , also unwraps !!
path: path.into(),
};
}
}
/// flag component needed to signify the intent to spawn a Blueprint
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct SpawnBlueprint;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// flag component marking any spwaned child of blueprints
pub struct FromBlueprint;
#[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<Entity>);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// You can add this component to a blueprint instance, and the instance will be hidden until it is ready
/// You usually want to use this for worlds/level spawning , or dynamic spawning at runtime, but not when you are adding blueprint instances to an existing entity
/// as it would first become invisible before re-appearing again
pub struct HideUntilReady;
#[derive(Component)]
/// marker component, gets added to all children of a currently spawning blueprint instance, can be usefull to avoid manipulating still in progress entities
pub struct BlueprintInstanceDisabled;
#[derive(Event, Debug)]
pub enum BlueprintEvent {
/// event fired when a blueprint instance has finished loading all of its assets & before it attempts spawning
AssetsLoaded {
entity: Entity,
blueprint_name: String,
blueprint_path: String,
// TODO: add assets list ?
},
/// event fired when a blueprint instance has completely finished spawning, ie
/// - all its assests have been loaded
/// - all of its child blueprint instances are ready
/// - all the post processing is finished (aabb calculation, material replacements etc)
InstanceReady {
entity: Entity,
blueprint_name: String,
blueprint_path: String,
},
}
// TODO: move this somewhere else ?
#[derive(Component, Reflect, Debug, Default)]
#[reflect(Component)]
/// component used to mark any entity as Dynamic: aka add this to make sure your entity is going to be saved
pub struct DynamicBlueprintInstance;
// TODO: move these somewhere else ?
#[derive(Component, Reflect, Debug, Default)]
#[reflect(Component)]
/// component gets added when a blueprint starts spawning, removed when spawning is completely done
pub struct BlueprintSpawning;
use gltf::Gltf as RawGltf;
/*
Overview of the Blueprint Spawning process
- Blueprint Load Assets
- Blueprint Assets Ready: spawn Blueprint's scene
- Blueprint Scene Ready (SceneInstance component is present):
- get list of sub Blueprints if any, inject sub blueprints spawn tracker
- Blueprint copy components to original entity, remove useless nodes
- Blueprint post process
- generate aabb (need full hierarchy in its final form)
- inject materials from library if needed
- Blueprint Ready
- bubble information up to parent blueprint instance
- if all sub_blueprints are ready => Parent blueprint Instance is ready
=> distinguish between blueprint instances inside blueprint instances vs blueprint instances inside blueprints ??
*/
pub(crate) fn blueprints_prepare_spawn(
blueprint_instances_to_spawn: Query<
(Entity, &BlueprintInfo, Option<&Name>),
Added<SpawnBlueprint>,
>,
mut commands: Commands,
asset_server: Res<AssetServer>,
// for hot reload
mut assets_to_blueprint_instances: ResMut<AssetToBlueprintInstancesMapper>,
// for debug
all_names: Query<&Name>
) {
for (entity, blueprint_info, entity_name) in blueprint_instances_to_spawn.iter() {
info!(
"BLUEPRINT: to spawn detected: {:?} path:{:?}",
blueprint_info.name, blueprint_info.path
);
//println!("all assets {:?}", all_assets);
//////////////
// we add the asset of the blueprint itself
// TODO: add detection of already loaded data
let untyped_handle = asset_server.load_untyped(&blueprint_info.path);
let asset_id = untyped_handle.id();
let loaded = asset_server.is_loaded_with_dependencies(asset_id);
let mut asset_infos: Vec<AssetLoadTracker> = vec![];
if !loaded {
asset_infos.push(AssetLoadTracker {
name: blueprint_info.name.clone(),
path: blueprint_info.path.clone(),
id: asset_id,
loaded: false,
handle: untyped_handle.clone(),
});
}
// and we also add all its assets
/* prefetch attempt */
let gltf = RawGltf::open(format!("assets/{}", blueprint_info.path)).unwrap();
for scene in gltf.scenes() {
if let Some(scene_extras) = scene.extras().clone()
{
let lookup: HashMap<String, Value> = serde_json::from_str(scene_extras.get()).unwrap();
if lookup.contains_key("BlueprintAssets") {
let assets_raw = &lookup["BlueprintAssets"];
//println!("ASSETS RAW {}", assets_raw);
let all_assets: BlueprintAssets =
ron::from_str(assets_raw.as_str().unwrap()).unwrap();
// println!("all_assets {:?}", all_assets);
for asset in all_assets.assets.iter() {
println!("ASSET {}",asset.path);
let untyped_handle = asset_server.load_untyped(&asset.path);
let asset_id = untyped_handle.id();
let loaded = asset_server.is_loaded_with_dependencies(asset_id);
if !loaded {
asset_infos.push(AssetLoadTracker {
name: asset.name.clone(),
path: asset.path.clone(),
id: asset_id,
loaded: false,
handle: untyped_handle.clone(),
});
}
// FIXME: dang, too early, asset server has not yet started loading yet
// let path_id = asset_server.get_path_id(&asset.path).expect("we should have alread checked for this asset");
let path_id = asset.path.clone();
// TODO: make this dependant on if hot reload is enabled or not
if !assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids.contains_key(&path_id) {
assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids.insert(path_id.clone(), vec![]);
}
// only insert if not already present in mapping
if !assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids[&path_id].contains(&entity) {
println!("adding mapping between {} and entity {:?}", path_id, all_names.get(entity));
assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids.get_mut(&path_id).unwrap().push(entity);
}
}
}
}
}
// now insert load tracker
// if there are assets to load
if !asset_infos.is_empty() {
commands.entity(entity).insert((
BlueprintAssetsLoadState {
all_loaded: false,
asset_infos,
..Default::default()
},
BlueprintAssetsNotLoaded,
));
} else {
commands.entity(entity).insert(BlueprintAssetsLoaded);
}
// if the entity has no name, add one based on the blueprint's
commands
.entity(entity)
.insert(bevy::prelude::Name::from(blueprint_info.name.clone()));
// add the blueprint spawning marker
commands.entity(entity).insert(BlueprintSpawning);
}
}
/// This system tracks & updates the loading state of all blueprints assets
pub(crate) fn blueprints_check_assets_loading(
mut blueprint_assets_to_load: Query<
(Entity, &BlueprintInfo, &mut BlueprintAssetsLoadState),
With<BlueprintAssetsNotLoaded>,
>,
asset_server: Res<AssetServer>,
mut commands: Commands,
mut blueprint_events: EventWriter<BlueprintEvent>,
) {
for (entity, blueprint_info, 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);
let mut failed = false;
if let bevy::asset::LoadState::Failed(_) = asset_server.load_state(asset_id) { 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;
assets_to_load.progress = progress;
// println!("LOADING: in progress for ALL assets of {:?} (instance of {}): {} ",entity_name, blueprint_info.path, progress * 100.0);
if all_loaded {
assets_to_load.all_loaded = true;
// println!("LOADING: DONE for ALL assets of {:?} (instance of {}), preparing for spawn", entity_name, blueprint_info.path);
blueprint_events.send(BlueprintEvent::AssetsLoaded {
entity,
blueprint_name: blueprint_info.name.clone(),
blueprint_path: blueprint_info.path.clone(),
});
commands
.entity(entity)
.insert(BlueprintAssetsLoaded)
.remove::<BlueprintAssetsNotLoaded>()
//.remove::<BlueprintAssetsLoadState>() //REMOVE this component in release mode/ when hot reload is off, keep it for dev/hot reload
;
}
}
}
pub(crate) fn blueprints_assets_loaded(
spawn_placeholders: Query<
(
Entity,
&BlueprintInfo,
Option<&Transform>,
Option<&Parent>,
Option<&AddToGameWorld>,
Option<&Name>,
Option<&HideUntilReady>,
Option<&AnimationInfos>,
),
(
With<BlueprintAssetsLoaded>,
Added<BlueprintAssetsLoaded>,
Without<BlueprintAssetsNotLoaded>,
),
>,
all_children: Query<&Children>,
mut game_world: Query<Entity, With<GameWorldTag>>,
assets_gltf: Res<Assets<Gltf>>,
asset_server: Res<AssetServer>,
mut graphs: ResMut<Assets<AnimationGraph>>,
mut commands: Commands,
) {
for (
entity,
blueprint_info,
transform,
original_parent,
add_to_world,
name,
hide_until_ready,
animation_infos,
) in spawn_placeholders.iter()
{
/*info!(
"BLUEPRINT: all assets loaded, attempting to spawn blueprint SCENE {:?} for entity {:?}, id: {:}, parent:{:?}",
blueprint_info.name, name, entity, original_parent
);*/
info!(
"BLUEPRINT: all assets loaded, attempting to spawn blueprint SCENE {:?} for entity {:?}, id: {}",
blueprint_info.name, name, entity
);
// info!("attempting to spawn {:?}", model_path);
let model_handle: Handle<Gltf> = asset_server.load(blueprint_info.path.clone()); // FIXME: kinda weird now
let blueprint_gltf = assets_gltf.get(&model_handle).unwrap_or_else(|| {
panic!(
"gltf file {:?} should have been loaded",
&blueprint_info.path
)
});
// 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 = blueprint_gltf
.named_scenes
.keys()
.next()
.expect("there should be at least one named scene in the gltf file to spawn");
let scene = &blueprint_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<Entity> = vec![];
if let Ok(c) = all_children.get(entity) {
for child in c.iter() {
original_children.push(*child);
}
}
// TODO: not a fan of this
// prepare data for animations
let mut graph = AnimationGraph::new();
let mut named_animations: HashMap<String, Handle<AnimationClip>> = HashMap::new();
let mut named_indices: HashMap<String, AnimationNodeIndex> = HashMap::new();
for (key, clip) in blueprint_gltf.named_animations.iter() {
named_animations.insert(key.to_string(), clip.clone());
let animation_index = graph.add_clip(clip.clone(), 1.0, graph.root);
named_indices.insert(key.to_string(), animation_index);
}
let graph = graphs.add(graph);
println!("Named animations : {:?}", named_animations.keys());
println!("ANIMATION INFOS: {:?}", animation_infos);
commands.entity(entity).insert((
SceneBundle {
scene: scene.clone(),
transform: transforms,
..Default::default()
},
OriginalChildren(original_children),
BlueprintAnimations {
// TODO: perhaps swap this out with SceneAnimations depending on whether we are spawning a level or a simple blueprint
// these are animations specific to the blueprint
named_animations,
named_indices,
graph,
},
));
if original_parent.is_none() {
// only allow hiding until ready when the entity does not have a parent (?)
if hide_until_ready.is_some() {
commands.entity(entity).insert(Visibility::Hidden); // visibility:
}
// only allow automatically adding a newly spawned blueprint instance to the "world", if the entity does not have a parent
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);
}
}
}
}
#[derive(Component, Reflect, Debug, Default)]
#[reflect(Component)]
pub struct SubBlueprintsSpawnTracker {
pub sub_blueprint_instances: HashMap<Entity, bool>,
}
#[derive(Component, Reflect, Debug)]
#[reflect(Component)]
pub struct SubBlueprintSpawnRoot(pub Entity);
#[derive(Component, Reflect, Debug)]
#[reflect(Component)]
pub struct BlueprintSceneSpawned;
#[derive(Component, Reflect, Debug)]
#[reflect(Component)]
pub struct BlueprintChildrenReady;
pub(crate) fn blueprints_scenes_spawned(
spawned_blueprint_scene_instances: Query<
(
Entity,
Option<&Name>,
Option<&Children>,
Option<&SubBlueprintSpawnRoot>,
),
(With<BlueprintSpawning>, Added<SceneInstance>),
>,
with_blueprint_infos: Query<(Entity, Option<&Name>), With<BlueprintInfo>>,
all_children: Query<&Children>,
all_parents: Query<&Parent>,
// mut sub_blueprint_trackers: Query<(Entity, &mut SubBlueprintsSpawnTracker, &BlueprintInfo)>,
mut commands: Commands,
all_names: Query<&Name>,
) {
for (entity, name, children, track_root) in spawned_blueprint_scene_instances.iter() {
info!(
"Done spawning blueprint scene for entity named {:?} (track root: {:?})",
name, track_root
);
let mut sub_blueprint_instances: Vec<Entity> = vec![];
let mut sub_blueprint_instance_names: Vec<Name> = vec![];
let mut tracker_data: HashMap<Entity, bool> = HashMap::new();
if track_root.is_none() {
for parent in all_parents.iter_ancestors(entity) {
if with_blueprint_infos.get(parent).is_ok() {
println!(
"found a parent with blueprint_info {:?} for {:?}",
all_names.get(parent),
all_names.get(entity)
);
commands
.entity(entity)
.insert(SubBlueprintSpawnRoot(parent)); // Injecting to know which entity is the root
break;
}
}
}
if children.is_some() {
for child in all_children.iter_descendants(entity) {
if with_blueprint_infos.get(child).is_ok() {
// println!("Parent blueprint instance of {:?} is {:?}", all_names.get(child), all_names.get(entity));
for parent in all_parents.iter_ancestors(child) {
if with_blueprint_infos.get(parent).is_ok() {
if parent == entity {
//println!("yohoho");
println!(
"Parent blueprint instance of {:?} is {:?}",
all_names.get(child),
all_names.get(parent)
);
commands.entity(child).insert(SubBlueprintSpawnRoot(entity)); // Injecting to know which entity is the root
tracker_data.insert(child, false);
sub_blueprint_instances.push(child);
if let Ok(nname) = all_names.get(child) {
sub_blueprint_instance_names.push(nname.clone());
}
/*if track_root.is_some() {
let prev_root = track_root.unwrap().0;
// if we already had a track root, and it is different from the current entity , change the previous track root's list of children
if prev_root != entity {
let mut tracker = sub_blueprint_trackers.get_mut(prev_root).expect("should have a tracker");
tracker.1.sub_blueprint_instances.remove(&child);
}
}*/
}
break;
}
}
}
// Mark all components as "Disabled" (until Bevy gets this as first class feature)
commands.entity(child).insert(BlueprintInstanceDisabled);
}
}
println!("sub blueprint instances {:?}", sub_blueprint_instance_names);
if tracker_data.keys().len() > 0 {
commands.entity(entity).insert(SubBlueprintsSpawnTracker {
sub_blueprint_instances: tracker_data.clone(),
});
} else {
commands.entity(entity).insert(BlueprintChildrenReady);
}
}
}
// could be done differently, by notifying each parent of a spawning blueprint that this child is done spawning ?
// perhaps using component hooks or observers (ie , if a ComponentSpawning + Parent)
use crate::CopyComponents;
use std::any::TypeId;
#[derive(Component, Reflect, Debug)]
#[reflect(Component)]
pub struct BlueprintReadyForPostProcess;
/// this system is in charge of doing component transfers & co
/// - 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 adds an `AnimationLink` component containing the entity that has the `AnimationPlayer` so that animations can be controlled from the original entity
pub(crate) fn blueprints_cleanup_spawned_scene(
blueprint_scenes: Query<
(
Entity,
&Children,
&OriginalChildren,
Option<&Name>,
&BlueprintAnimations,
),
Added<BlueprintChildrenReady>,
>,
animation_players: Query<(Entity, &Parent), With<AnimationPlayer>>,
all_children: Query<&Children>,
all_parents: Query<&Parent>,
with_animation_infos: Query<&AnimationInfos>,
// FIXME: meh
anims: Query<&BlueprintAnimations>,
mut commands: Commands,
all_names: Query<&Name>,
) {
for (original, children, original_children, name, animations) in
blueprint_scenes.iter()
{
info!("YOOO ready !! removing empty nodes {:?}", 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 blueprint_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 child in children.iter() {
if !original_children.0.contains(child) {
blueprint_root_entity = *child;
break;
}
}
// we flag all children of the blueprint instance with 'FromBlueprint'
// can be usefull to filter out anything that came from blueprints vs normal children
for child in all_children.iter_descendants(blueprint_root_entity) {
commands.entity(child).insert(FromBlueprint); // we do this here in order to avoid doing it to normal children
}
// copy components into from blueprint instance's blueprint_root_entity to original entity
commands.add(CopyComponents {
source: blueprint_root_entity,
destination: original,
exclude: vec![TypeId::of::<Parent>(), TypeId::of::<Children>()],
stringent: false,
});
// we move all of children of the blueprint instance one level to the original entity to avoid having an additional, useless nesting level
if let Ok(root_entity_children) = all_children.get(blueprint_root_entity) {
for child in root_entity_children.iter() {
// info!("copying child {:?} upward from {:?} to {:?}", names.get(*child), blueprint_root_entity, original);
commands.entity(original).add_child(*child);
}
}
if animations.named_animations.keys().len() > 0 {
for (entity_with_player, parent) in animation_players.iter() {
if parent.get() == blueprint_root_entity {
println!(
"FOUND ANIMATION PLAYER FOR {:?} {:?} ",
all_names.get(original),
all_names.get(entity_with_player)
);
// 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(entity_with_player),)); // FIXME : this is only valid for per-blueprint logic, no per scene animations
// since v0.14 you need both AnimationTransitions and AnimationGraph components/handle on the same entity as the animationPlayer
let transitions = AnimationTransitions::new();
commands
.entity(entity_with_player)
.insert((transitions, animations.graph.clone()));
}
}
// VERY convoluted, but it works
for child in all_children.iter_descendants(blueprint_root_entity) {
if with_animation_infos.get(child).is_ok() {
// player is already on the same entity as the animation_infos
if animation_players.get(child).is_ok() {
println!(
"found BLUEPRINT animation player for {:?} at {:?} Root: {:?}",
all_names.get(child),
all_names.get(child),
all_names.get(original)
);
commands
.entity(original)
.insert((
//BlueprintAnimationPlayerLink(bla),
BlueprintAnimationInfosLink(child)
))
;
} else {
for parent in all_parents.iter_ancestors(child) {
if animation_players.get(parent).is_ok() {
println!(
"found SCENE animation player for {:?} at {:?} Root: {:?}",
all_names.get(child),
all_names.get(parent),
all_names.get(original)
);
println!("INSERTING SCENE ANIMATIONS INTO");
let original_animations = anims.get(original).unwrap();
commands.entity(child).insert((
SceneAnimationPlayerLink(parent),
SceneAnimations {
named_animations: original_animations
.named_animations
.clone(),
named_indices: original_animations.named_indices.clone(),
graph: original_animations.graph.clone(),
},
));
}
if with_animation_infos.get(parent).is_ok() {
commands.entity(child).insert(SceneAnimationInfosLink(parent));
}
}
}
}
}
}
commands
.entity(original)
.remove::<BlueprintChildrenReady>() // we are done with this step, we can remove the `BlueprintChildrenReady` tag component
.insert(BlueprintReadyForPostProcess); // Tag the entity so any systems dealing with post processing can know it is now their "turn"
commands.entity(blueprint_root_entity).despawn_recursive(); // Remove the root entity that comes from the spawned-in scene
}
}
#[derive(Component, Reflect, Debug)]
#[reflect(Component)]
pub struct BlueprintReadyForFinalizing;
#[derive(Component, Debug)]
/// flag component added when a Blueprint instance ist Ready : ie :
/// - its assets have loaded
/// - it has finished spawning
pub struct BlueprintInstanceReady;
pub(crate) fn blueprints_finalize_instances(
blueprint_instances: Query<
(
Entity,
Option<&Name>,
&BlueprintInfo,
Option<&SubBlueprintSpawnRoot>,
Option<&HideUntilReady>,
),
(With<BlueprintSpawning>, With<BlueprintReadyForFinalizing>),
>,
mut sub_blueprint_trackers: Query<&mut SubBlueprintsSpawnTracker, With<BlueprintInfo>>,
spawning_blueprints: Query<&BlueprintSpawning>,
all_children: Query<&Children>,
mut blueprint_events: EventWriter<BlueprintEvent>,
mut commands: Commands,
all_names: Query<&Name>
) {
for (entity, name, blueprint_info, parent_blueprint, hide_until_ready) in
blueprint_instances.iter()
{
info!("Finalizing blueprint instance {:?}", name);
commands
.entity(entity)
.remove::<BlueprintReadyForFinalizing>()
.remove::<BlueprintReadyForPostProcess>()
.remove::<BlueprintSpawning>()
.remove::<SpawnBlueprint>()
//.remove::<Handle<Scene>>(); // FIXME: if we delete the handle to the scene, things get despawned ! not what we want
//.remove::<BlueprintAssetsLoadState>(); // also clear the sub assets tracker to free up handles, perhaps just freeing up the handles and leave the rest would be better ?
//.remove::<BlueprintAssetsLoaded>();
.insert(BlueprintInstanceReady);
// Deal with sub blueprints
// now check if the current entity is a child blueprint instance of another entity
// this should always be done last, as children should be finished before the parent can be processed correctly
// TODO: perhaps use observers for these
if let Some(track_root) = parent_blueprint {
// only propagate sub_blueprint spawning if the parent blueprint instance ist actually in spawning mode
if spawning_blueprints.get(track_root.0).is_ok() {
if let Ok(mut tracker) = sub_blueprint_trackers.get_mut(track_root.0) {
tracker
.sub_blueprint_instances
.entry(entity)
.or_insert(true);
tracker.sub_blueprint_instances.insert(entity, true);
// TODO: ugh, my limited rust knowledge, this is bad code
let mut all_spawned = true;
for val in tracker.sub_blueprint_instances.values() {
if !val {
all_spawned = false;
break;
}
}
if all_spawned {
let root_name = all_names.get(track_root.0);
println!("ALLLLL SPAAAAWNED for {} named {:?}", track_root.0, root_name);
commands.entity(track_root.0).insert(BlueprintChildrenReady);
}
}
}
}
for child in all_children.iter_descendants(entity) {
commands.entity(child).remove::<BlueprintInstanceDisabled>();
}
if hide_until_ready.is_some() {
commands.entity(entity).insert(Visibility::Visible);
}
blueprint_events.send(BlueprintEvent::InstanceReady {
entity,
blueprint_name: blueprint_info.name.clone(),
blueprint_path: blueprint_info.path.clone(),
});
}
}

View File

@ -0,0 +1,182 @@
use bevy::core_pipeline::tonemapping::Tonemapping;
use bevy::pbr::DirectionalLightShadowMap;
use bevy::prelude::*;
use bevy::render::view::{ColorGrading, ColorGradingGlobal, ColorGradingSection};
use crate::GltfComponentsSet;
pub(crate) fn plugin(app: &mut App) {
app.register_type::<BlenderBackgroundShader>()
.register_type::<BlenderShadowSettings>()
.register_type::<BlenderLightShadows>()
.register_type::<BlenderToneMapping>()
.register_type::<BlenderColorGrading>()
.add_systems(
Update,
(
process_lights,
process_shadowmap,
process_background_shader,
process_tonemapping,
process_colorgrading,
)
.after(GltfComponentsSet::Injection),
);
}
#[derive(Component, Reflect, Default, Debug, PartialEq, Clone)]
#[reflect(Component)]
#[non_exhaustive]
/// The properties of a light's shadow , to enable controlling per light shadows from Blender
pub struct BlenderLightShadows {
pub enabled: bool,
pub buffer_bias: f32,
}
/// The background color as described by Blender's [background shader](https://docs.blender.org/manual/en/latest/render/shader_nodes/shader/background.html).
#[derive(Component, Reflect, Default, Debug, PartialEq, Clone)]
#[reflect(Component)]
#[non_exhaustive]
pub struct BlenderBackgroundShader {
pub color: Color,
pub strength: f32,
}
/// The settings used by EEVEE's [shadow rendering](https://docs.blender.org/manual/en/latest/render/eevee/render_settings/shadows.html).
#[derive(Component, Reflect, Default, Debug, PartialEq, Clone)]
#[reflect(Component)]
#[non_exhaustive]
pub struct BlenderShadowSettings {
pub cascade_size: usize,
}
/// Not all possible Blender `ToneMappings` are available in Bevy & vice versa
#[derive(Component, Reflect, Default, Debug, PartialEq, Clone)]
#[reflect(Component)]
#[non_exhaustive]
pub enum BlenderToneMapping {
#[default]
None,
AgX,
Filmic,
}
#[derive(Component, Reflect, Default, Debug, PartialEq, Clone)]
#[reflect(Component)]
#[non_exhaustive]
pub struct BlenderColorGrading {
exposure: f32,
gamma: f32,
}
fn process_lights(
mut directional_lights: Query<
(&mut DirectionalLight, Option<&BlenderLightShadows>),
Added<DirectionalLight>,
>,
mut spot_lights: Query<(&mut SpotLight, Option<&BlenderLightShadows>), Added<SpotLight>>,
mut point_lights: Query<(&mut PointLight, Option<&BlenderLightShadows>), Added<PointLight>>,
) {
for (mut light, blender_light_shadows) in directional_lights.iter_mut() {
if let Some(blender_light_shadows) = blender_light_shadows {
light.shadows_enabled = blender_light_shadows.enabled;
}
}
for (mut light, blender_light_shadows) in spot_lights.iter_mut() {
if let Some(blender_light_shadows) = blender_light_shadows {
light.shadows_enabled = blender_light_shadows.enabled;
}
}
for (mut light, blender_light_shadows) in point_lights.iter_mut() {
if let Some(blender_light_shadows) = blender_light_shadows {
light.shadows_enabled = blender_light_shadows.enabled;
}
}
}
fn process_shadowmap(
shadowmaps: Query<&BlenderShadowSettings, Added<BlenderShadowSettings>>,
mut commands: Commands,
) {
for shadowmap in shadowmaps.iter() {
commands.insert_resource(DirectionalLightShadowMap {
size: shadowmap.cascade_size,
});
}
}
fn process_background_shader(
background_shaders: Query<&BlenderBackgroundShader, Added<BlenderBackgroundShader>>,
mut commands: Commands,
) {
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,
});
commands.insert_resource(ClearColor(background_shader.color));
}
}
// FIXME: this logic should not depend on if toneMapping or Cameras where added first
fn process_tonemapping(
tonemappings: Query<(Entity, &BlenderToneMapping), Added<BlenderToneMapping>>,
cameras: Query<Entity, With<Camera>>,
mut commands: Commands,
) {
for entity in cameras.iter() {
for (scene_id, tone_mapping) in tonemappings.iter() {
match tone_mapping {
BlenderToneMapping::None => {
//println!("TONEMAPPING NONE");
commands.entity(entity).remove::<Tonemapping>();
}
BlenderToneMapping::AgX => {
//println!("TONEMAPPING Agx");
commands.entity(entity).insert(Tonemapping::AgX);
}
BlenderToneMapping::Filmic => {
//println!("TONEMAPPING Filmic");
commands.entity(entity).insert(Tonemapping::BlenderFilmic);
}
}
commands.entity(scene_id).remove::<BlenderToneMapping>();
}
}
}
// FIXME: this logic should not depend on if toneMapping or Cameras where added first
fn process_colorgrading(
blender_colorgradings: Query<(Entity, &BlenderColorGrading), Added<BlenderColorGrading>>,
cameras: Query<Entity, With<Camera>>,
mut commands: Commands,
) {
for entity in cameras.iter() {
for (scene_id, blender_colorgrading) in blender_colorgradings.iter() {
info!("COLOR GRADING");
commands.entity(entity).insert(ColorGrading {
global: ColorGradingGlobal {
exposure: blender_colorgrading.exposure,
..Default::default()
},
shadows: ColorGradingSection {
gamma: blender_colorgrading.gamma,
..Default::default()
},
midtones: ColorGradingSection {
gamma: blender_colorgrading.gamma,
..Default::default()
},
highlights: ColorGradingSection {
gamma: blender_colorgrading.gamma,
..Default::default()
},
..Default::default()
});
commands.entity(scene_id).remove::<ColorGrading>();
}
}
}

View File

@ -10,13 +10,7 @@ pub use process_gltfs::*;
pub mod blender_settings;
use bevy::{
app::Startup,
ecs::{
component::Component,
reflect::ReflectComponent,
system::{Res, Resource},
},
log::warn,
ecs::{component::Component, reflect::ReflectComponent},
prelude::{App, IntoSystemConfigs, Plugin, SystemSet, Update},
reflect::Reflect,
};
@ -29,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)
@ -65,35 +59,13 @@ pub enum GltfComponentsSet {
Injection,
}
#[derive(Clone, Resource)]
pub struct GltfComponentsConfig {
pub(crate) legacy_mode: bool,
}
pub struct ComponentsFromGltfPlugin {
pub legacy_mode: bool,
}
impl Default for ComponentsFromGltfPlugin {
fn default() -> Self {
Self { legacy_mode: true }
}
}
fn check_for_legacy_mode(gltf_components_config: Res<GltfComponentsConfig>) {
if gltf_components_config.legacy_mode {
warn!("using simplified component definitions is deprecated since 0.3, prefer defining components with real ron values (use the bevy_components tool for Blender for simplicity) ");
}
}
#[derive(Default)]
pub struct ComponentsFromGltfPlugin {}
impl Plugin for ComponentsFromGltfPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(blender_settings::plugin)
.register_type::<GltfProcessed>()
.insert_resource(GltfComponentsConfig {
legacy_mode: self.legacy_mode,
})
.add_systems(Startup, check_for_legacy_mode)
.add_systems(
Update,
(add_components_from_gltf_extras).in_set(GltfComponentsSet::Injection),

View File

@ -0,0 +1,157 @@
use bevy::{
core::Name,
ecs::{
entity::Entity,
query::{Added, Without},
reflect::{AppTypeRegistry, ReflectComponent},
world::World,
},
gltf::{GltfExtras, GltfMaterialExtras, GltfMeshExtras, GltfSceneExtras},
hierarchy::Parent,
log::debug,
reflect::{Reflect, TypeRegistration},
utils::HashMap,
};
use crate::{ronstring_to_reflect_component, GltfProcessed};
// , mut entity_components: HashMap<Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>>
fn find_entity_components(
entity: Entity,
name: Option<&Name>,
parent: Option<&Parent>,
reflect_components: Vec<(Box<dyn Reflect>, TypeRegistration)>,
entity_components: &HashMap<Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>>,
) -> (Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>) {
// we assign the components specified /xxx_components objects to their parent node
let mut target_entity = entity;
// if the node contains "components" or ends with "_pa" (ie add to parent), the components will not be added to the entity itself but to its parent
// this is mostly used for Blender collections
if parent.is_some() {
if let Some(name) = name {
if name.as_str().contains("components") || name.as_str().ends_with("_pa") {
debug!("adding components to parent");
target_entity = parent.expect("the target entity had a parent ").get();
}
}
}
debug!("adding to {:?}", target_entity);
// if there where already components set to be added to this entity (for example when entity_data was refering to a parent), update the vec of entity_components accordingly
// this allows for example blender collection to provide basic ecs data & the instances to override/ define their own values
if entity_components.contains_key(&target_entity) {
let mut updated_components: Vec<(Box<dyn Reflect>, TypeRegistration)> = Vec::new();
let current_components = &entity_components[&target_entity];
// first inject the current components
for (component, type_registration) in current_components {
updated_components.push((component.clone_value(), type_registration.clone()));
}
// then inject the new components: this also enables overwrite components set in the collection
for (component, type_registration) in reflect_components {
updated_components.push((component.clone_value(), type_registration));
}
return (target_entity, updated_components);
}
(target_entity, reflect_components)
}
/// main function: injects components into each entity in gltf files that have `gltf_extras`, using reflection
pub fn add_components_from_gltf_extras(world: &mut World) {
let mut extras = world.query_filtered::<(Entity, Option<&Name>, &GltfExtras, Option<&Parent>), (Added<GltfExtras>, Without<GltfProcessed>)>();
let mut scene_extras = world.query_filtered::<(Entity, Option<&Name>, &GltfSceneExtras, Option<&Parent>), (Added<GltfSceneExtras>, Without<GltfProcessed>)>();
let mut mesh_extras = world.query_filtered::<(Entity, Option<&Name>, &GltfMeshExtras, Option<&Parent>), (Added<GltfMeshExtras>, Without<GltfProcessed>)>();
let mut material_extras = world.query_filtered::<(Entity, Option<&Name>, &GltfMaterialExtras, Option<&Parent>), (Added<GltfMaterialExtras>, Without<GltfProcessed>)>();
let mut entity_components: HashMap<Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>> =
HashMap::new();
// let gltf_components_config = world.resource::<GltfComponentsConfig>();
for (entity, name, extra, parent) in extras.iter(world) {
debug!(
"Gltf Extra: Name: {:?}, entity {:?}, parent: {:?}, extras {:?}",
name, entity, parent, extra
);
let type_registry: &AppTypeRegistry = world.resource();
let type_registry = type_registry.read();
let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry);
// let name = name.unwrap_or(&Name::new(""));
let (target_entity, updated_components) =
find_entity_components(entity, name, parent, reflect_components, &entity_components);
entity_components.insert(target_entity, updated_components);
}
for (entity, name, extra, parent) in scene_extras.iter(world) {
debug!(
"Gltf Scene Extra: Name: {:?}, entity {:?}, parent: {:?}, scene_extras {:?}",
name, entity, parent, extra
);
let type_registry: &AppTypeRegistry = world.resource();
let type_registry = type_registry.read();
let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry);
let (target_entity, updated_components) =
find_entity_components(entity, name, parent, reflect_components, &entity_components);
entity_components.insert(target_entity, updated_components);
}
for (entity, name, extra, parent) in mesh_extras.iter(world) {
debug!(
"Gltf Mesh Extra: Name: {:?}, entity {:?}, parent: {:?}, mesh_extras {:?}",
name, entity, parent, extra
);
let type_registry: &AppTypeRegistry = world.resource();
let type_registry = type_registry.read();
let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry);
let (target_entity, updated_components) =
find_entity_components(entity, name, parent, reflect_components, &entity_components);
entity_components.insert(target_entity, updated_components);
}
for (entity, name, extra, parent) in material_extras.iter(world) {
debug!(
"Name: {:?}, entity {:?}, parent: {:?}, material_extras {:?}",
name, entity, parent, extra
);
let type_registry: &AppTypeRegistry = world.resource();
let type_registry = type_registry.read();
let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry);
let (target_entity, updated_components) =
find_entity_components(entity, name, parent, reflect_components, &entity_components);
entity_components.insert(target_entity, updated_components);
}
for (entity, components) in entity_components {
let type_registry: &AppTypeRegistry = world.resource();
let type_registry = type_registry.clone();
let type_registry = type_registry.read();
if !components.is_empty() {
debug!("--entity {:?}, components {}", entity, components.len());
}
for (component, type_registration) in components {
debug!(
"------adding {} {:?}",
component.get_represented_type_info().unwrap().type_path(),
component
);
{
let mut entity_mut = world.entity_mut(entity);
type_registration
.data::<ReflectComponent>()
.expect("Unable to reflect component")
.insert(&mut entity_mut, &*component, &type_registry);
entity_mut.insert(GltfProcessed); //
}
}
}
}

View File

@ -0,0 +1,133 @@
use bevy::log::{debug, warn};
use bevy::reflect::serde::ReflectDeserializer;
use bevy::reflect::{Reflect, TypeRegistration, TypeRegistry};
use bevy::utils::HashMap;
use ron::Value;
use serde::de::DeserializeSeed;
use super::capitalize_first_letter;
pub fn ronstring_to_reflect_component(
ron_string: &str,
type_registry: &TypeRegistry,
) -> Vec<(Box<dyn Reflect>, TypeRegistration)> {
let lookup: HashMap<String, Value> = ron::from_str(ron_string).unwrap();
let mut components: Vec<(Box<dyn Reflect>, TypeRegistration)> = Vec::new();
// println!("ron_string {:?}", ron_string);
for (name, value) in lookup.into_iter() {
let parsed_value: String = match value.clone() {
Value::String(str) => str,
_ => ron::to_string(&value).unwrap().to_string(),
};
if name.as_str() == "bevy_components" {
bevy_components_string_to_components(parsed_value, type_registry, &mut components);
} else {
components_string_to_components(
name,
value,
parsed_value,
type_registry,
&mut components,
);
}
}
components
}
fn components_string_to_components(
name: String,
value: Value,
parsed_value: String,
type_registry: &TypeRegistry,
components: &mut Vec<(Box<dyn Reflect>, TypeRegistration)>,
) {
let type_string = name.replace("component: ", "").trim().to_string();
let capitalized_type_name = capitalize_first_letter(type_string.as_str());
if let Some(type_registration) =
type_registry.get_with_short_type_path(capitalized_type_name.as_str())
{
debug!("TYPE INFO {:?}", type_registration.type_info());
let ron_string = format!(
"{{ \"{}\":{} }}",
type_registration.type_info().type_path(),
parsed_value
);
/*
// usefull to determine what an entity looks like Serialized
let test_struct = Color::Srgba(Srgba { red: 0.2, green: 0.2, blue: 0.2, alpha: 0.2 });
//CameraRenderGraph::new("name");
let serializer = ReflectSerializer::new(&test_struct, &type_registry);
let serialized =
ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();
println!("serialized Component {}", serialized);
*/
debug!("component data ron string {}", ron_string);
let mut deserializer = ron::Deserializer::from_str(ron_string.as_str())
.expect("deserialzer should have been generated from string");
let reflect_deserializer = ReflectDeserializer::new(type_registry);
let component = reflect_deserializer
.deserialize(&mut deserializer)
.unwrap_or_else(|_| {
panic!(
"failed to deserialize component {} with value: {:?}",
name, value
)
});
debug!("component {:?}", component);
debug!("real type {:?}", component.get_represented_type_info());
components.push((component, type_registration.clone()));
debug!("found type registration for {}", capitalized_type_name);
} else {
warn!("no type registration for {}", capitalized_type_name);
}
}
fn bevy_components_string_to_components(
parsed_value: String,
type_registry: &TypeRegistry,
components: &mut Vec<(Box<dyn Reflect>, TypeRegistration)>,
) {
let lookup: HashMap<String, Value> = ron::from_str(&parsed_value).unwrap();
for (key, value) in lookup.into_iter() {
let parsed_value: String = match value.clone() {
Value::String(str) => str,
_ => ron::to_string(&value).unwrap().to_string(),
};
if let Some(type_registration) = type_registry.get_with_type_path(key.as_str()) {
debug!("TYPE INFO {:?}", type_registration.type_info());
let ron_string = format!(
"{{ \"{}\":{} }}",
type_registration.type_info().type_path(),
parsed_value
);
debug!("component data ron string {}", ron_string);
let mut deserializer = ron::Deserializer::from_str(ron_string.as_str())
.expect("deserialzer should have been generated from string");
let reflect_deserializer = ReflectDeserializer::new(type_registry);
let component = reflect_deserializer
.deserialize(&mut deserializer)
.unwrap_or_else(|_| {
panic!(
"failed to deserialize component {} with value: {:?}",
key, value
)
});
debug!("component {:?}", component);
debug!("real type {:?}", component.get_represented_type_info());
components.push((component, type_registration.clone()));
debug!("found type registration for {}", key);
} else {
warn!("no type registration for {}", key);
}
}
}

82
crates/blenvy/src/lib.rs Normal file
View File

@ -0,0 +1,82 @@
use bevy::{prelude::*, render::primitives::Aabb, utils::HashMap};
use std::path::PathBuf;
pub mod components;
pub use components::*;
pub mod registry;
pub use registry::*;
pub mod blueprints;
pub use blueprints::*;
#[derive(Clone, Resource)]
pub struct BlenvyConfig {
// registry
pub(crate) registry_save_path: PathBuf,
pub(crate) registry_component_filter: SceneFilter,
#[allow(dead_code)]
pub(crate) registry_resource_filter: SceneFilter,
// blueprints
pub(crate) aabbs: bool,
pub(crate) aabb_cache: HashMap<String, Aabb>, // cache for aabbs
pub(crate) materials_cache: HashMap<String, Handle<StandardMaterial>>, // cache for materials
// save & load
pub(crate) save_component_filter: SceneFilter,
pub(crate) save_resource_filter: SceneFilter,
}
#[derive(Debug, Clone)]
/// Plugin for gltf blueprints
pub struct BlenvyPlugin {
pub registry_save_path: PathBuf,
pub registry_component_filter: SceneFilter,
pub registry_resource_filter: SceneFilter,
/// 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 {
fn default() -> Self {
Self {
registry_save_path: PathBuf::from("registry.json"), // relative to assets folder
registry_component_filter: SceneFilter::default(),
registry_resource_filter: SceneFilter::default(),
aabbs: false,
save_component_filter: SceneFilter::default(),
save_resource_filter: SceneFilter::default(),
}
}
}
impl Plugin for BlenvyPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
ComponentsFromGltfPlugin::default(),
ExportRegistryPlugin::default(),
BlueprintsPlugin::default(),
))
.insert_resource(BlenvyConfig {
registry_save_path: self.registry_save_path.clone(),
registry_component_filter: self.registry_component_filter.clone(),
registry_resource_filter: self.registry_resource_filter.clone(),
aabbs: self.aabbs,
aabb_cache: HashMap::new(),
materials_cache: HashMap::new(),
save_component_filter: self.save_component_filter.clone(),
save_resource_filter: self.save_resource_filter.clone()
});
}
}

View File

@ -1,40 +1,47 @@
use std::{fs::File, path::Path};
use bevy::log::info;
use bevy_ecs::{
reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},
world::World,
use crate::{AssetRoot, BlenvyConfig};
use bevy::{
log::info,
prelude::{AppTypeRegistry, ReflectComponent, ReflectResource, World},
reflect::{TypeInfo, TypeRegistration, VariantInfo},
};
use bevy_reflect::{TypeInfo, TypeRegistration, VariantInfo}; // TypePath // DynamicTypePath
use serde_json::{json, Map, Value};
use crate::{AssetRoot, ExportComponentsConfig};
use std::{fs::File, path::Path};
pub fn export_types(world: &mut World) {
let config = world
.get_resource::<ExportComponentsConfig>()
.get_resource::<BlenvyConfig>()
.expect("ExportComponentsConfig should exist at this stage");
let asset_root = world.resource::<AssetRoot>();
let registry_save_path = Path::join(&asset_root.0, &config.save_path);
println!("registry_save_path {}", registry_save_path.display());
let registry_save_path = Path::join(&asset_root.0, &config.registry_save_path);
let writer = File::create(registry_save_path).expect("should have created schema file");
let components_to_filter_out = &config.registry_component_filter.clone();
let resources_to_filter_out = &config.registry_resource_filter.clone();
let types = world.resource_mut::<AppTypeRegistry>();
let types = types.read();
let schemas = types.iter().map(export_type).collect::<Map<_, _>>();
let schemas = types
.iter()
.filter(|type_info| {
let type_id = type_info.type_id();
components_to_filter_out.is_allowed_by_id(type_id)
&& resources_to_filter_out.is_allowed_by_id(type_id)
})
.map(export_type)
.collect::<Map<_, _>>();
serde_json::to_writer_pretty(
writer,
&json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "bevy component registry schema",
"long_name": "bevy component registry schema",
"$defs": schemas,
}),
)
.expect("valid json");
info!("Done exporting registry schema")
info!("Done exporting registry schema");
}
pub fn export_type(reg: &TypeRegistration) -> (String, Value) {
@ -57,7 +64,7 @@ pub fn export_type(reg: &TypeRegistration) -> (String, Value) {
json!({
"type": "object",
"typeInfo": "Struct",
"title": t.type_path(),
"long_name": t.type_path(),
"properties": properties,
"additionalProperties": false,
"required": info
@ -75,7 +82,7 @@ pub fn export_type(reg: &TypeRegistration) -> (String, Value) {
json!({
"type": "string",
"typeInfo": "Enum",
"title": t.type_path(),
"long_name": t.type_path(),
"oneOf": info
.iter()
.map(|variant| match variant {
@ -94,12 +101,12 @@ pub fn export_type(reg: &TypeRegistration) -> (String, Value) {
VariantInfo::Struct(v) => json!({
"type": "object",
"typeInfo": "Struct",
"title": v.name(),
"long_name": v.name(),
"short_name": v.name().split("::").last().unwrap_or(v.name()),
"properties": v
.iter()
.enumerate()
.map(|(variant_idx, field)| (field.name().to_owned(), add_min_max(json!({"type": typ(field.type_path()), "title": field.name()}), reg, field_idx, Some(variant_idx))))
.map(|(variant_idx, field)| (field.name().to_owned(), add_min_max(json!({"type": typ(field.type_path()), "long_name": field.name()}), reg, field_idx, Some(variant_idx))))
.collect::<Map<_, _>>(),
"additionalProperties": false,
"required": v
@ -111,7 +118,7 @@ pub fn export_type(reg: &TypeRegistration) -> (String, Value) {
VariantInfo::Tuple(v) => json!({
"type": "array",
"typeInfo": "Tuple",
"title": v.name(),
"long_name": v.name(),
"short_name":v.name(),
"prefixItems": v
.iter()
@ -121,7 +128,7 @@ pub fn export_type(reg: &TypeRegistration) -> (String, Value) {
"items": false,
}),
VariantInfo::Unit(v) => json!({
"title": v.name(),
"long_name": v.name(),
}),
})
.collect::<Vec<_>>();
@ -129,13 +136,13 @@ pub fn export_type(reg: &TypeRegistration) -> (String, Value) {
json!({
"type": "object",
"typeInfo": "Enum",
"title": t.type_path(),
"long_name": t.type_path(),
"oneOf": variants,
})
}
}
TypeInfo::TupleStruct(info) => json!({
"title": t.type_path(),
"long_name": t.type_path(),
"type": "array",
"typeInfo": "TupleStruct",
"prefixItems": info
@ -147,26 +154,27 @@ pub fn export_type(reg: &TypeRegistration) -> (String, Value) {
}),
TypeInfo::List(info) => {
json!({
"title": t.type_path(),
"long_name": t.type_path(),
"type": "array",
"typeInfo": "List",
"items": json!({"type": typ(info.item_type_path_table().path())}),
})
}
TypeInfo::Array(info) => json!({
"title": t.type_path(),
"long_name": t.type_path(),
"type": "array",
"typeInfo": "Array",
"items": json!({"type": typ(info.item_type_path_table().path())}),
}),
TypeInfo::Map(info) => json!({
"title": t.type_path(),
"long_name": t.type_path(),
"type": "object",
"typeInfo": "Map",
"additionalProperties": json!({"type": typ(info.value_type_path_table().path())}),
"valueType": json!({"type": typ(info.value_type_path_table().path())}),
"keyType": json!({"type": typ(info.key_type_path_table().path())}),
}),
TypeInfo::Tuple(info) => json!({
"title": t.type_path(),
"long_name": t.type_path(),
"type": "array",
"typeInfo": "Tuple",
"prefixItems": info
@ -177,7 +185,7 @@ pub fn export_type(reg: &TypeRegistration) -> (String, Value) {
"items": false,
}),
TypeInfo::Value(info) => json!({
"title": t.type_path(),
"long_name": t.type_path(),
"type": map_json_type(info.type_path()),
"typeInfo": "Value",
}),

View File

@ -1,26 +1,15 @@
pub mod export_types;
use std::path::PathBuf;
use bevy_app::Startup;
use bevy_ecs::system::Resource;
pub mod export_types;
pub use export_types::*;
use bevy::{
app::Startup,
asset::AssetPlugin,
prelude::{App, Plugin},
prelude::{App, Plugin, Resource},
scene::SceneFilter,
};
// Plugin configuration
#[derive(Clone, Resource)]
pub struct ExportComponentsConfig {
pub(crate) save_path: PathBuf,
#[allow(dead_code)]
pub(crate) component_filter: SceneFilter, // unused for now
#[allow(dead_code)]
pub(crate) resource_filter: SceneFilter, // unused for now
}
pub struct ExportRegistryPlugin {
pub component_filter: SceneFilter,
pub resource_filter: SceneFilter,
@ -30,8 +19,8 @@ pub struct ExportRegistryPlugin {
impl Default for ExportRegistryPlugin {
fn default() -> Self {
Self {
component_filter: SceneFilter::default(), // unused for now
resource_filter: SceneFilter::default(), // unused for now
component_filter: SceneFilter::default(),
resource_filter: SceneFilter::default(),
save_path: PathBuf::from("registry.json"), // relative to assets folder
}
}
@ -39,13 +28,7 @@ impl Default for ExportRegistryPlugin {
impl Plugin for ExportRegistryPlugin {
fn build(&self, app: &mut App) {
app.register_asset_root()
.insert_resource(ExportComponentsConfig {
save_path: self.save_path.clone(),
component_filter: self.component_filter.clone(),
resource_filter: self.resource_filter.clone(),
})
.add_systems(Startup, export_types);
app.register_asset_root().add_systems(Startup, export_types);
}
}

View File

@ -1,4 +1,4 @@
# Examples
This folder contains numerous examples showing how to use both bevy_gltf_components and bevy_gltf_blueprints.
Each example is its own crate so its dependencies are specific & clear.
This folder contains numerous examples showing how to use Blenvy
Each example is its own crate so its dependencies and assets are specific & clear.

View File

@ -1,12 +0,0 @@
[package]
name = "bevy_gltf_blueprints_animation_example"
version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] }
bevy_gltf_blueprints = { path = "../../../crates/bevy_gltf_blueprints" }
bevy_gltf_worlflow_examples_common_rapier = { path = "../../common_rapier" }
bevy_rapier3d = { version = "0.27.0", features = ["serde-serialize", "debug-render-3d", "enhanced-determinism"] }
rand = "0.8.5"

View File

@ -1,6 +0,0 @@
({
"world":File (path: "models/Level1.glb"),
"models": Folder (
path: "models/library",
),
})

View File

@ -1,14 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_blueprints::*;
pub struct CorePlugin;
impl Plugin for CorePlugin {
fn build(&self, app: &mut App) {
app.add_plugins((BlueprintsPlugin {
library_folder: "models/library".into(),
format: GltfFormat::GLB,
aabbs: true,
..Default::default()
},));
}
}

View File

@ -1,312 +0,0 @@
use bevy_gltf_worlflow_examples_common_rapier::{
assets::GameAssets, GameState, InAppRunning, Player,
};
use bevy_rapier3d::prelude::Velocity;
use rand::Rng;
use std::time::Duration;
use bevy::prelude::*;
use bevy_gltf_blueprints::{
AnimationPlayerLink, Animations, BluePrintBundle, BlueprintName, GameWorldTag,
};
use super::{Fox, Robot};
pub fn setup_game(
mut commands: Commands,
game_assets: Res<GameAssets>,
models: Res<Assets<bevy::gltf::Gltf>>,
mut next_game_state: ResMut<NextState<GameState>>,
) {
commands.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 0.2,
});
// here we actually spawn our game world/level
commands.spawn((
SceneBundle {
// note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene
scene: models
.get(game_assets.world.clone().unwrap().id())
.expect("main level should have been loaded")
.scenes[0]
.clone(),
..default()
},
bevy::prelude::Name::from("world"),
GameWorldTag,
InAppRunning,
));
next_game_state.set(GameState::InGame)
}
pub fn spawn_test(
keycode: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) {
if keycode.just_pressed(KeyCode::KeyT) {
let world = game_world.single_mut();
let world = world.1[0];
let mut rng = rand::thread_rng();
let range = 8.5;
let x: f32 = rng.gen_range(-range..range);
let y: f32 = rng.gen_range(-range..range);
let mut rng = rand::thread_rng();
let range = 0.8;
let vel_x: f32 = rng.gen_range(-range..range);
let vel_y: f32 = rng.gen_range(2.0..2.5);
let vel_z: f32 = rng.gen_range(-range..range);
let name_index: u64 = rng.gen();
let new_entity = commands
.spawn((
BluePrintBundle {
blueprint: BlueprintName("Fox".to_string()),
..Default::default()
},
bevy::prelude::Name::from(format!("Spawned{}", name_index)),
// BlueprintName("Health_Pickup".to_string()),
// SpawnHere,
TransformBundle::from_transform(Transform::from_xyz(x, 0.0, y)),
Velocity {
linvel: Vec3::new(vel_x, vel_y, vel_z),
angvel: Vec3::new(0.0, 0.0, 0.0),
},
))
.id();
commands.entity(world).add_child(new_entity);
}
}
// example of changing animation of entities based on proximity to the player, for "fox" entities (Tag component)
pub fn animation_change_on_proximity_foxes(
players: Query<&GlobalTransform, With<Player>>,
animated_foxes: Query<(&GlobalTransform, &AnimationPlayerLink, &Animations), With<Fox>>,
mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
) {
for player_transforms in players.iter() {
for (fox_tranforms, link, animations) in animated_foxes.iter() {
let distance = player_transforms
.translation()
.distance(fox_tranforms.translation());
let mut anim_name = "Walk";
if distance < 8.5 {
anim_name = "Run";
} else if (8.5..10.0).contains(&distance) {
anim_name = "Walk";
} else if (10.0..15.0).contains(&distance) {
anim_name = "Survey";
}
// now play the animation based on the chosen animation name
let (mut animation_player, mut animation_transitions) =
animation_players.get_mut(link.0).unwrap();
animation_transitions
.play(
&mut animation_player,
*animations
.named_indices
.get(anim_name)
.expect("animation name should be in the list"),
Duration::from_secs(3),
)
.repeat();
}
}
}
// example of changing animation of entities based on proximity to the player, this time for the "robot" entities (Tag component)
pub fn animation_change_on_proximity_robots(
players: Query<&GlobalTransform, With<Player>>,
animated_robots: Query<(&GlobalTransform, &AnimationPlayerLink, &Animations), With<Robot>>,
mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
) {
for player_transforms in players.iter() {
for (robot_tranforms, link, animations) in animated_robots.iter() {
let distance = player_transforms
.translation()
.distance(robot_tranforms.translation());
let mut anim_name = "Idle";
if distance < 8.5 {
anim_name = "Jump";
} else if (8.5..10.0).contains(&distance) {
anim_name = "Scan";
} else if (10.0..15.0).contains(&distance) {
anim_name = "Idle";
}
// now play the animation based on the chosen animation name
let (mut animation_player, mut animation_transitions) =
animation_players.get_mut(link.0).unwrap();
animation_transitions
.play(
&mut animation_player,
*animations
.named_indices
.get(anim_name)
.expect("animation name should be in the list"),
Duration::from_secs(3),
)
.repeat();
}
}
}
pub fn animation_control(
animated_enemies: Query<(&AnimationPlayerLink, &Animations), With<Robot>>,
animated_foxes: Query<(&AnimationPlayerLink, &Animations), With<Fox>>,
mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
keycode: Res<ButtonInput<KeyCode>>,
// mut entities_with_animations : Query<(&mut AnimationPlayer, &mut Animations)>,
) {
// robots
if keycode.just_pressed(KeyCode::KeyB) {
for (link, animations) in animated_enemies.iter() {
let (mut animation_player, mut animation_transitions) =
animation_players.get_mut(link.0).unwrap();
let anim_name = "Scan";
animation_transitions
.play(
&mut animation_player,
*animations
.named_indices
.get(anim_name)
.expect("animation name should be in the list"),
Duration::from_secs(5),
)
.repeat();
}
}
// foxes
if keycode.just_pressed(KeyCode::KeyW) {
for (link, animations) in animated_foxes.iter() {
let (mut animation_player, mut animation_transitions) =
animation_players.get_mut(link.0).unwrap();
let anim_name = "Walk";
animation_transitions
.play(
&mut animation_player,
*animations
.named_indices
.get(anim_name)
.expect("animation name should be in the list"),
Duration::from_secs(5),
)
.repeat();
}
}
if keycode.just_pressed(KeyCode::KeyX) {
for (link, animations) in animated_foxes.iter() {
let (mut animation_player, mut animation_transitions) =
animation_players.get_mut(link.0).unwrap();
let anim_name = "Run";
animation_transitions
.play(
&mut animation_player,
*animations
.named_indices
.get(anim_name)
.expect("animation name should be in the list"),
Duration::from_secs(5),
)
.repeat();
}
}
if keycode.just_pressed(KeyCode::KeyC) {
for (link, animations) in animated_foxes.iter() {
let (mut animation_player, mut animation_transitions) =
animation_players.get_mut(link.0).unwrap();
let anim_name = "Survey";
animation_transitions
.play(
&mut animation_player,
*animations
.named_indices
.get(anim_name)
.expect("animation name should be in the list"),
Duration::from_secs(5),
)
.repeat();
}
}
/* Improveement ideas for the future
// a bit more ideal API
if keycode.just_pressed(KeyCode::B) {
for (animation_player, animations) in animated_enemies.iter() {
let anim_name = "Scan";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
animation_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
}
// even better API
if keycode.just_pressed(KeyCode::B) {
for (animation_player, animations) in animated_enemies.iter() {
animation_player.play_with_transition("Scan", Duration::from_secs(5)).repeat(); // with a merged animationPlayer + animations storage
// alternative, perhaps more realistic, and better seperation of concerns
animation_player.play_with_transition(animations, "Scan", Duration::from_secs(5)).repeat();
}
}*/
/*for (mut anim_player, animations) in entities_with_animations.iter_mut(){
if keycode.just_pressed(KeyCode::W) {
let anim_name = "Walk";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
if keycode.just_pressed(KeyCode::X) {
let anim_name = "Run";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
if keycode.just_pressed(KeyCode::C) {
let anim_name = "Survey";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
if keycode.just_pressed(KeyCode::S) {
let anim_name = "Scan";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
if keycode.just_pressed(KeyCode::I) {
let anim_name = "Idle";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
}*/
}

View File

@ -1,40 +0,0 @@
pub mod in_game;
pub use in_game::*;
pub mod in_main_menu;
pub use in_main_menu::*;
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common_rapier::{AppState, GameState};
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// Demo marker component
pub struct Fox;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// Demo marker component
pub struct Robot;
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.register_type::<Robot>()
.register_type::<Fox>()
.add_systems(
Update,
(
spawn_test,
animation_control,
animation_change_on_proximity_foxes,
animation_change_on_proximity_robots,
)
.run_if(in_state(GameState::InGame)),
)
.add_systems(OnEnter(AppState::MenuRunning), setup_main_menu)
.add_systems(OnExit(AppState::MenuRunning), teardown_main_menu)
.add_systems(Update, main_menu.run_if(in_state(AppState::MenuRunning)))
.add_systems(OnEnter(AppState::AppRunning), setup_game);
}
}

View File

@ -1,12 +0,0 @@
[package]
name = "bevy_gltf_blueprints_basic_example"
version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] }
bevy_gltf_blueprints = { path = "../../../crates/bevy_gltf_blueprints" }
bevy_gltf_worlflow_examples_common_rapier = { path = "../../common_rapier" }
bevy_rapier3d = { version = "0.27.0", features = ["serde-serialize", "debug-render-3d", "enhanced-determinism"] }
rand = "0.8.5"

View File

@ -1,6 +0,0 @@
({
"world":File (path: "models/World.glb"),
"models": Folder (
path: "models/library",
),
})

View File

@ -1,14 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_blueprints::*;
pub struct CorePlugin;
impl Plugin for CorePlugin {
fn build(&self, app: &mut App) {
app.add_plugins((BlueprintsPlugin {
library_folder: "models/library".into(),
format: GltfFormat::GLB,
aabbs: true,
..Default::default()
},));
}
}

View File

@ -1,126 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_blueprints::{BluePrintBundle, BlueprintName, GameWorldTag};
use bevy_gltf_worlflow_examples_common_rapier::{assets::GameAssets, GameState, InAppRunning};
use bevy_rapier3d::prelude::Velocity;
use rand::Rng;
pub fn setup_game(
mut commands: Commands,
game_assets: Res<GameAssets>,
models: Res<Assets<bevy::gltf::Gltf>>,
mut next_game_state: ResMut<NextState<GameState>>,
) {
commands.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 0.2,
});
// here we actually spawn our game world/level
commands.spawn((
SceneBundle {
// note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene
scene: models
.get(game_assets.world.clone().unwrap().id())
.expect("main level should have been loaded")
.scenes[0]
.clone(),
..default()
},
bevy::prelude::Name::from("world"),
GameWorldTag,
InAppRunning,
));
next_game_state.set(GameState::InGame)
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct UnregisteredComponent;
pub fn spawn_test(
keycode: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) {
if keycode.just_pressed(KeyCode::KeyT) {
let world = game_world.single_mut();
let world = world.1[0];
let mut rng = rand::thread_rng();
let range = 5.5;
let x: f32 = rng.gen_range(-range..range);
let y: f32 = rng.gen_range(-range..range);
let mut rng = rand::thread_rng();
let range = 0.8;
let vel_x: f32 = rng.gen_range(-range..range);
let vel_y: f32 = rng.gen_range(2.0..2.5);
let vel_z: f32 = rng.gen_range(-range..range);
let name_index: u64 = rng.gen();
let new_entity = commands
.spawn((
BluePrintBundle {
blueprint: BlueprintName("Health_Pickup".to_string()),
..Default::default()
},
bevy::prelude::Name::from(format!("test{}", name_index)),
// BlueprintName("Health_Pickup".to_string()),
// SpawnHere,
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
Velocity {
linvel: Vec3::new(vel_x, vel_y, vel_z),
angvel: Vec3::new(0.0, 0.0, 0.0),
},
))
.id();
commands.entity(world).add_child(new_entity);
}
}
pub fn spawn_test_unregisted_components(
keycode: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) {
if keycode.just_pressed(KeyCode::KeyU) {
let world = game_world.single_mut();
let world = world.1[0];
let mut rng = rand::thread_rng();
let range = 5.5;
let x: f32 = rng.gen_range(-range..range);
let y: f32 = rng.gen_range(-range..range);
let mut rng = rand::thread_rng();
let range = 0.8;
let vel_x: f32 = rng.gen_range(-range..range);
let vel_y: f32 = rng.gen_range(2.0..2.5);
let vel_z: f32 = rng.gen_range(-range..range);
let name_index: u64 = rng.gen();
let new_entity = commands
.spawn((
BluePrintBundle {
blueprint: BlueprintName("Health_Pickup".to_string()),
..Default::default()
},
bevy::prelude::Name::from(format!("test{}", name_index)),
// BlueprintName("Health_Pickup".to_string()),
// SpawnHere,
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
Velocity {
linvel: Vec3::new(vel_x, vel_y, vel_z),
angvel: Vec3::new(0.0, 0.0, 0.0),
},
UnregisteredComponent,
))
.id();
commands.entity(world).add_child(new_entity);
}
}

View File

@ -1,121 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common_rapier::{AppState, InMainMenu};
pub fn setup_main_menu(mut commands: Commands) {
commands.spawn((
Camera2dBundle {
camera: Camera {
order: 102, // needed because of this: https://github.com/jakobhellermann/bevy_editor_pls/blob/crates/bevy_editor_pls_default_windows/src/cameras/mod.rs#L213C9-L213C28
..default()
},
..Default::default()
},
InMainMenu,
));
commands.spawn((
TextBundle::from_section(
"SOME GAME TITLE !!",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 18.0,
color: Color::WHITE,
..Default::default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(100.0),
left: Val::Px(200.0),
..default()
}),
InMainMenu,
));
commands.spawn((
TextBundle::from_section(
"New Game (press Enter to start, press T once the game is started for demo spawning)",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 18.0,
color: Color::WHITE,
..Default::default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(200.0),
left: Val::Px(200.0),
..default()
}),
InMainMenu,
));
/*
commands.spawn((
TextBundle::from_section(
"Load Game",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 18.0,
color: Color::WHITE,
..Default::default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(250.0),
left: Val::Px(200.0),
..default()
}),
InMainMenu
));
commands.spawn((
TextBundle::from_section(
"Exit Game",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 18.0,
color: Color::WHITE,
..Default::default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(300.0),
left: Val::Px(200.0),
..default()
}),
InMainMenu
));*/
}
pub fn teardown_main_menu(bla: Query<Entity, With<InMainMenu>>, mut commands: Commands) {
for bli in bla.iter() {
commands.entity(bli).despawn_recursive();
}
}
pub fn main_menu(
keycode: Res<ButtonInput<KeyCode>>,
mut next_app_state: ResMut<NextState<AppState>>,
// mut next_game_state: ResMut<NextState<GameState>>,
// mut save_requested_events: EventWriter<SaveRequest>,
// mut load_requested_events: EventWriter<LoadRequest>,
) {
if keycode.just_pressed(KeyCode::Enter) {
next_app_state.set(AppState::AppLoading);
// next_game_state.set(GameState::None);
}
if keycode.just_pressed(KeyCode::KeyL) {
next_app_state.set(AppState::AppLoading);
// load_requested_events.send(LoadRequest { path: "toto".into() })
}
if keycode.just_pressed(KeyCode::KeyS) {
// save_requested_events.send(SaveRequest { path: "toto".into() })
}
}

View File

@ -1,22 +0,0 @@
pub mod in_game;
pub use in_game::*;
pub mod in_main_menu;
pub use in_main_menu::*;
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common_rapier::{AppState, GameState};
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(spawn_test, spawn_test_unregisted_components).run_if(in_state(GameState::InGame)),
)
.add_systems(OnEnter(AppState::MenuRunning), setup_main_menu)
.add_systems(OnExit(AppState::MenuRunning), teardown_main_menu)
.add_systems(Update, main_menu.run_if(in_state(AppState::MenuRunning)))
.add_systems(OnEnter(AppState::AppRunning), setup_game);
}
}

View File

@ -1,24 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common_rapier::CommonPlugin;
mod core;
use crate::core::*;
mod game;
use game::*;
mod test_components;
use test_components::*;
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(AssetPlugin::default()),
// our custom plugins
CommonPlugin,
CorePlugin, // reusable plugins
GamePlugin, // specific to our game
ComponentsTestPlugin, // Showcases different type of components /structs
))
.run();
}

View File

@ -1,12 +0,0 @@
[package]
name = "bevy_gltf_blueprints_basic_xpbd_physics_example"
version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] }
bevy_gltf_blueprints = { path = "../../../crates/bevy_gltf_blueprints" }
bevy_gltf_worlflow_examples_common_xpbd = { path = "../../common_xpbd" }
bevy_xpbd_3d = "0.5"
rand = "0.8.5"

View File

@ -1,15 +0,0 @@
# Basic xpbd physics example/demo
Same as the basic example but using [xpbd](https://github.com/Jondolf/bevy_xpbd) instead of Rapier [rapier](https://github.com/dimforge/bevy_rapier)
## Running this example
```
cargo run --features bevy/dynamic_linking
```
### Additional notes
* You usually define either the Components directly or use ```Proxy components``` that get replaced in Bevy systems with the actual Components that you want (usually when for some reason, ie external crates with unregistered components etc) you cannot use the components directly.
* this example contains code for future features, not finished yet ! please disregard anything related to saving & loading

View File

@ -1,6 +0,0 @@
({
"world":File (path: "models/World.glb"),
"models": Folder (
path: "models/library",
),
})

Some files were not shown because too many files have changed in this diff Show More