chore(): added all boilerplate
* switch to ron for components inside Blender * related experiments & cleanups * total overhaul & cleanup of physics proxies handling * a lot of cleanups
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
*.blend1
|
4383
Cargo.lock
generated
Normal file
27
Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "blender-worklfow"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bevy="0.11.0"
|
||||||
|
bevy_editor_pls = { git="https://github.com/jakobhellermann/bevy_editor_pls.git" }
|
||||||
|
bevy_rapier3d = { version = "0.22.0", features = [ "serde-serialize", "debug-render-3d", "enhanced-determinism"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde = "*"
|
||||||
|
ron="*"
|
||||||
|
|
||||||
|
#### --------------------Dev/ debug-------------------------------
|
||||||
|
# Enable high optimizations for dependencies (incl. Bevy), but not for our code:
|
||||||
|
[profile.dev.package."*"]
|
||||||
|
opt-level = 3
|
||||||
|
|
||||||
|
[profile.dev.package.bevy]
|
||||||
|
features = ["dynamic"]
|
||||||
|
|
||||||
|
#### --------------------Production/ release-------------------------------
|
||||||
|
[profile.release]
|
||||||
|
strip = "debuginfo"
|
||||||
|
lto = "thin"
|
132
README.md
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
|
||||||
|
# Blender_gltf_components
|
||||||
|
|
||||||
|
![demo](./_docs/blender_gltf_components.png)
|
||||||
|
|
||||||
|
|
||||||
|
This example, is actually closer to a boilerplate + tooling showcases how to use a minimalistic [Blender](https://www.blender.org/) (gltf) centric workflow for [Bevy](https://bevyengine.org/), ie defining entites & their components
|
||||||
|
inside Blender using Blender's objects **custom properties**.
|
||||||
|
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 .
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
The workflow goes as follows (once you got your Bevy code setup)
|
||||||
|
- add the ```process_gltf``` [module code](./process_gltf/)
|
||||||
|
- add the ```ProcessGltfPlugin```, to your app
|
||||||
|
```rust
|
||||||
|
.add_plugins((
|
||||||
|
ProcessGltfPlugin
|
||||||
|
))
|
||||||
|
```
|
||||||
|
(and any other ```proxy``` processing plugins that you need )
|
||||||
|
see [here](./main.rs#69)
|
||||||
|
|
||||||
|
- 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)
|
||||||
|
|
||||||
|
|
||||||
|
- Create a mesh/ collection (for reuse) in Blender
|
||||||
|
- Go to object properties => add a **STRING** property, and add your component data
|
||||||
|
- unit structs, enums, and more complex strucs / components are all supported, if the fields are basic data types
|
||||||
|
- for structs with no params: use ```null``` as a value
|
||||||
|
- for structs with params: use a json representation of your fields (see below)
|
||||||
|
|
||||||
|
![unit struct components in Blender](./_docs/components_blender.png)
|
||||||
|
|
||||||
|
In rust:
|
||||||
|
|
||||||
|
![unit struct components in Bevy](./_docs/demo_simple_components.png)
|
||||||
|
|
||||||
|
(the Rust struct for these components for reference is [here](./main.rs#40) )
|
||||||
|
|
||||||
|
|
||||||
|
![complex components in Blender](./_docs/components_blender_parameters.png)
|
||||||
|
|
||||||
|
In rust:
|
||||||
|
|
||||||
|
![complex components in Blender](./_docs/camera_tracking_component.png)
|
||||||
|
|
||||||
|
(the Rust struct for this component for reference is [here](./camera/camera_tracking.rs#14) )
|
||||||
|
|
||||||
|
- for collections & their instances:
|
||||||
|
* I usually create a library scene with nested collections
|
||||||
|
* the leaf collections are the assets you use in your level
|
||||||
|
* add an empty called xxxx_components
|
||||||
|
* add the components as explained in the previous part
|
||||||
|
![blender collection asset](./_docs/blender_collections.png)
|
||||||
|
|
||||||
|
* In the Level/world itself, just create an instance of the collection (standard Blender, ie Shift+A -> collection instance -> pick the collection)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- export your level as a glb/gltf file :
|
||||||
|
!!**IMPORTANT** you need to check the following:
|
||||||
|
- custom properties
|
||||||
|
- cameras & lights if you want a complete level (as in this example)
|
||||||
|
|
||||||
|
![gltf_export](./_docs/gltf_export.png)
|
||||||
|
|
||||||
|
|
||||||
|
- load it in Bevy (see the demo main file for this)
|
||||||
|
- you should see the components attached to your entities in Bevy
|
||||||
|
|
||||||
|
![components in bevy](./_docs/components_bevy.png)
|
||||||
|
![components in bevy](./_docs/components_bevy2.png)
|
||||||
|
![components in bevy](./_docs/components_bevy3.png)
|
||||||
|
|
||||||
|
|
||||||
|
> note: you get a warning if there are any unregistered components in your gltf file (they get ignored)
|
||||||
|
you will get a warning **per entity**
|
||||||
|
![missing components warnings](./_docs/component_warnings.png)
|
||||||
|
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
Included are the following modules / tools
|
||||||
|
* [```process_gltf```](./process_gltf/) the most important module: this is the one extracting ```component``` information from the gltf files
|
||||||
|
* [```insert_dependant_component```](/examples/blender_gltf_components/relationships/relationships_insert_dependant_components.rs) a small utility to automatically inject
|
||||||
|
components that are dependant on an other component
|
||||||
|
for example an Entity with a Player component should also always have a ShouldBeWithPlayer component
|
||||||
|
you get a warning if you use this though, as I consider this to be stop-gap solution (usually you should have either a bundle, or directly define all needed components)
|
||||||
|
* [```camera```](./camera/) an example post process/replace proxies plugin, for Camera that also adds CameraTracking functions (to enable a camera to follow an object, ie the player)
|
||||||
|
* [```lighting```](./lighting/) an other example post process/replace proxies plugin for lighting, that toggles shadows, lighting config, etc so that things look closer to the original Blender data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Feel free to use as you want, rip it appart, use any/all parts that you need !
|
||||||
|
|
||||||
|
This tooling and workflow has enabled me to go from a blank Bevy + Blender setup to a working barebones level in very little time (30 minutes or so ?) !
|
||||||
|
You can then add your own components & systems for your own gameplay very easilly
|
||||||
|
|
||||||
|
## Information
|
||||||
|
- the Bevy/ Rust code is [here](/examples/blender_gltf_components/main.rs)
|
||||||
|
- the Blender file is [here](../../assets/models/level.blend)
|
||||||
|
- I added [bevy_editor_pls](https://github.com/jakobhellermann/bevy_editor_pls) as a dependency for convenience so you can inspect your level/components
|
||||||
|
|
||||||
|
|
||||||
|
## Limitations / issues
|
||||||
|
- the components have to be defined in ```JSON``` in Blender, which is a relic of my initial implementation, not very practical, rather verbose, will likely try switching to RON at some point, or even use the AppTypeRegistry and some Python code on the Blender side for a nicer UI (although this loses the "fast & easy, no tooling" approach)
|
||||||
|
- the asset management in this example is stripped down for demo purposes, I normally use https://github.com/NiklasEi/bevy_asset_loader to define, organise and preload assets
|
||||||
|
(in a different state that comes before the actual game/level), so you will see some of the changes to the level/scene "flash by"
|
||||||
|
- I did not include `bevy_rapier`/physics code that I usually use to trim down the demo, but I kept some of the proxy items in the Blender scene, might add physics back as I wanted a cleaner way to define colliders from within Blender (currently it also goes via json)
|
||||||
|
|
||||||
|
## 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
|
||||||
|
* gltf spawning tools where you just need to preload gltf files then you can spawn 1...n entities defined in gltf files by name (for example enemies, powerups, etc)
|
||||||
|
* simplified animation logic: ie instead of having to manually specify the animations you need from a gltf file, it is integrated with the spawning system above, which creates a ```Animations``` component in all entities that have an ```AnimationPlayer``` and you can simply query for both to easilly control your animations per entity.
|
||||||
|
|
||||||
|
## 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 :)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This example, all its contents & assets is licensed under MIT.
|
BIN
_docs/blender_collections.png
Normal file
After Width: | Height: | Size: 315 KiB |
BIN
_docs/blender_gltf_components.png
Normal file
After Width: | Height: | Size: 142 KiB |
BIN
_docs/camera_tracking_component.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
_docs/component_registration.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
_docs/component_warnings.png
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
_docs/components_bevy.png
Normal file
After Width: | Height: | Size: 164 KiB |
BIN
_docs/components_bevy2.png
Normal file
After Width: | Height: | Size: 199 KiB |
BIN
_docs/components_bevy3.png
Normal file
After Width: | Height: | Size: 212 KiB |
BIN
_docs/components_blender.png
Normal file
After Width: | Height: | Size: 410 KiB |
BIN
_docs/components_blender_parameters.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
_docs/demo_simple_components.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
_docs/gltf_export.png
Normal file
After Width: | Height: | Size: 106 KiB |
BIN
assets/models/level1.glb
Normal file
BIN
assets/models/test.blend
Normal file
BIN
assets/models/test.glb
Normal file
3
assets/test.ron
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
((
|
||||||
|
blender_worklfow::Player: ()
|
||||||
|
))
|
34
src/camera/camera_replace_proxies.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::core_pipeline::bloom::{BloomSettings, BloomCompositeMode};
|
||||||
|
use bevy::core_pipeline::tonemapping::{Tonemapping, DebandDither};
|
||||||
|
|
||||||
|
use super::CameraTracking;
|
||||||
|
|
||||||
|
pub fn camera_replace_proxies (
|
||||||
|
mut commands: Commands,
|
||||||
|
mut added_cameras: Query<(Entity, &mut Camera), (Added<Camera>, With<CameraTracking>)>,
|
||||||
|
) {
|
||||||
|
|
||||||
|
for (entity, mut camera) in added_cameras.iter_mut(){
|
||||||
|
info!("detected added camera, updating proxy");
|
||||||
|
camera.hdr = true;
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(
|
||||||
|
DebandDither::Enabled
|
||||||
|
)
|
||||||
|
.insert(
|
||||||
|
Tonemapping::None
|
||||||
|
)
|
||||||
|
.insert(
|
||||||
|
BloomSettings{
|
||||||
|
intensity: 0.08,
|
||||||
|
composite_mode:BloomCompositeMode::Additive,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
58
src/camera/camera_tracking.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Component for cameras, with an offset from the Trackable target
|
||||||
|
///
|
||||||
|
pub struct CameraTracking{
|
||||||
|
pub offset: Vec3
|
||||||
|
}
|
||||||
|
impl Default for CameraTracking {
|
||||||
|
fn default() -> Self {
|
||||||
|
CameraTracking { offset: Vec3::new(0.0, 6.0, 8.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Debug, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Component for cameras, with an offset from the Trackable target
|
||||||
|
pub struct CameraTrackingOffset(Vec3);
|
||||||
|
impl Default for CameraTrackingOffset {
|
||||||
|
fn default() -> Self {
|
||||||
|
CameraTrackingOffset(Vec3::new(0.0, 6.0, 8.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CameraTrackingOffset {
|
||||||
|
fn new (input: Vec3) -> Self {
|
||||||
|
println!("NEEEW");
|
||||||
|
CameraTrackingOffset(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, )]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Add this component to an entity if you want it to be tracked by a Camera
|
||||||
|
pub struct CameraTrackable;
|
||||||
|
|
||||||
|
pub fn camera_track(
|
||||||
|
mut tracking_cameras: Query<(&mut Transform, &CameraTrackingOffset), (With<Camera>, With<CameraTrackingOffset>, Without<CameraTrackable>)>,
|
||||||
|
camera_tracked: Query<&Transform, With<CameraTrackable>>,
|
||||||
|
) {
|
||||||
|
|
||||||
|
for (mut camera_transform, tracking_offset) in tracking_cameras.iter_mut() {
|
||||||
|
for tracked_transform in camera_tracked.iter(){
|
||||||
|
|
||||||
|
let target_position = tracked_transform.translation + tracking_offset.0;
|
||||||
|
let eased_position = camera_transform.translation.lerp(target_position, 0.1);
|
||||||
|
camera_transform.translation = eased_position;// + tracking.offset;// tracked_transform.translation + tracking.offset;
|
||||||
|
*camera_transform = camera_transform.looking_at(tracked_transform.translation, Vec3::Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
src/camera/mod.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
pub mod camera_tracking;
|
||||||
|
pub use camera_tracking::*;
|
||||||
|
|
||||||
|
pub mod camera_replace_proxies;
|
||||||
|
pub use camera_replace_proxies::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct CameraPlugin;
|
||||||
|
impl Plugin for CameraPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
.register_type::<CameraTrackable>()
|
||||||
|
.register_type::<CameraTracking>()
|
||||||
|
.register_type::<CameraTrackingOffset>()
|
||||||
|
|
||||||
|
.add_systems(Update,
|
||||||
|
(
|
||||||
|
camera_replace_proxies,
|
||||||
|
camera_track,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
29
src/lighting/lighting_replace_proxies.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use bevy::pbr::{CascadeShadowConfigBuilder, CascadeShadowConfig};
|
||||||
|
|
||||||
|
// fixme might be too specific to might needs, should it be moved out ? also these are all for lights, not models
|
||||||
|
pub fn lighting_replace_proxies(
|
||||||
|
mut added_dirights: Query<(Entity, &mut DirectionalLight), Added<DirectionalLight>>,
|
||||||
|
mut added_spotlights: Query<&mut SpotLight, Added<SpotLight>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
|
||||||
|
){
|
||||||
|
|
||||||
|
for (entity, mut light) in added_dirights.iter_mut(){
|
||||||
|
light.illuminance *= 5.0;
|
||||||
|
light.shadows_enabled = true;
|
||||||
|
let shadow_config:CascadeShadowConfig = CascadeShadowConfigBuilder {
|
||||||
|
first_cascade_far_bound: 15.0,
|
||||||
|
maximum_distance: 135.0,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(shadow_config);
|
||||||
|
}
|
||||||
|
for mut light in added_spotlights.iter_mut(){
|
||||||
|
light.shadows_enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
18
src/lighting/mod.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
mod lighting_replace_proxies;
|
||||||
|
use lighting_replace_proxies::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::pbr::{NotShadowCaster, DirectionalLightShadowMap};
|
||||||
|
|
||||||
|
pub struct LightingPlugin;
|
||||||
|
impl Plugin for LightingPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
.insert_resource(DirectionalLightShadowMap { size: 4096 })
|
||||||
|
// FIXME: adding these since they are missing
|
||||||
|
.register_type::<NotShadowCaster>()
|
||||||
|
|
||||||
|
.add_systems(PreUpdate, lighting_replace_proxies) // FIXME: you should actually run this in a specific state most likely
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
223
src/main.rs
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
use bevy::{prelude::*, asset::ChangeWatcher, gltf::Gltf};
|
||||||
|
use bevy_editor_pls::prelude::*;
|
||||||
|
use bevy_rapier3d::prelude::*;
|
||||||
|
|
||||||
|
mod process_gltf;
|
||||||
|
use process_gltf::*;
|
||||||
|
|
||||||
|
mod camera;
|
||||||
|
use camera::*;
|
||||||
|
|
||||||
|
mod lighting;
|
||||||
|
use lighting::*;
|
||||||
|
|
||||||
|
mod relationships;
|
||||||
|
use relationships::*;
|
||||||
|
|
||||||
|
mod physics;
|
||||||
|
use physics::*;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, )]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo marker component
|
||||||
|
pub struct Player;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, )]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo component showing auto injection of components
|
||||||
|
pub struct ShouldBeWithPlayer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, )]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo marker component
|
||||||
|
pub struct LoadedMarker;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, )]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo marker component
|
||||||
|
pub struct Interactible;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, )]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo marker component
|
||||||
|
pub struct MeshCollider;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
|
||||||
|
enum AppState {
|
||||||
|
#[default]
|
||||||
|
Loading,
|
||||||
|
Running,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
use bevy::{prelude::*};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, )]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum SoundMaterial{
|
||||||
|
Metal,
|
||||||
|
Wood,
|
||||||
|
Rock,
|
||||||
|
Cloth,
|
||||||
|
Squishy,
|
||||||
|
#[default]
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main(){
|
||||||
|
App::new()
|
||||||
|
.add_plugins((
|
||||||
|
DefaultPlugins.set(
|
||||||
|
AssetPlugin {
|
||||||
|
// This tells the AssetServer to watch for changes to assets.
|
||||||
|
// It enables our scenes to automatically reload in game when we modify their files.
|
||||||
|
// practical in our case to be able to edit the shaders without needing to recompile
|
||||||
|
watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(50)),
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
// editor
|
||||||
|
EditorPlugin::default(),
|
||||||
|
// physics
|
||||||
|
RapierPhysicsPlugin::<NoUserData>::default(),
|
||||||
|
RapierDebugRenderPlugin::default(),
|
||||||
|
// our custom plugins
|
||||||
|
ProcessGltfPlugin,
|
||||||
|
LightingPlugin,
|
||||||
|
CameraPlugin,
|
||||||
|
PhysicsPlugin
|
||||||
|
))
|
||||||
|
|
||||||
|
.register_type::<Interactible>()
|
||||||
|
.register_type::<MeshCollider>()
|
||||||
|
.register_type::<SoundMaterial>()
|
||||||
|
|
||||||
|
|
||||||
|
.register_type::<Player>()
|
||||||
|
// little helper utility, to automatically inject components that are dependant on an other component
|
||||||
|
// ie, here an Entity with a Player component should also always have a ShouldBeWithPlayer component
|
||||||
|
// you get a warning if you use this, as I consider this to be stop-gap solution (usually you should have either a bundle, or directly define all needed components)
|
||||||
|
.add_systems(Update, insert_dependant_component::<Player, ShouldBeWithPlayer>)
|
||||||
|
|
||||||
|
|
||||||
|
.add_state::<AppState>()
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(Update, (
|
||||||
|
spawn_level.run_if(in_state(AppState::Loading)),
|
||||||
|
player_move_demo.run_if(in_state(AppState::Running)),
|
||||||
|
test_collision_events
|
||||||
|
))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct AssetLoadHack(Handle<Scene>);
|
||||||
|
// we preload the data here, but this is for DEMO PURPOSES ONLY !! Please use https://github.com/NiklasEi/bevy_asset_loader or a similar logic to seperate loading / pre processing
|
||||||
|
// of assets from the spawning
|
||||||
|
// AssetLoadHack is also just for the same purpose, you do not need it in a real scenario
|
||||||
|
// the states here are also for demo purposes only,
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
|
||||||
|
let tmp: Handle<Scene> = asset_server.load("models/level1.glb#Scene0");
|
||||||
|
commands.insert_resource(AssetLoadHack(tmp));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_level(
|
||||||
|
mut commands: Commands,
|
||||||
|
scene_markers: Query<&LoadedMarker>,
|
||||||
|
preloaded_scene: Res<AssetLoadHack>,
|
||||||
|
|
||||||
|
mut asset_event_reader: EventReader<AssetEvent<Gltf>>,
|
||||||
|
mut next_state: ResMut<NextState<AppState>>,
|
||||||
|
){
|
||||||
|
|
||||||
|
if let Some(asset_event) = asset_event_reader.iter().next() {
|
||||||
|
match asset_event {
|
||||||
|
AssetEvent::Created { handle: _ } => {
|
||||||
|
info!("GLTF loaded");
|
||||||
|
if scene_markers.is_empty() {
|
||||||
|
info!("spawning scene");
|
||||||
|
commands.spawn(
|
||||||
|
(
|
||||||
|
SceneBundle {
|
||||||
|
scene: preloaded_scene.0.clone(),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
LoadedMarker,
|
||||||
|
Name::new("Level1")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
next_state.set(AppState::Running);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn player_move_demo(
|
||||||
|
keycode: Res<Input<KeyCode>>,
|
||||||
|
mut players: Query<&mut Transform, With<Player>>,
|
||||||
|
){
|
||||||
|
|
||||||
|
let speed = 0.2;
|
||||||
|
if let Ok(mut player) = players.get_single_mut() {
|
||||||
|
if keycode.pressed(KeyCode::Left) {
|
||||||
|
player.translation.x += speed;
|
||||||
|
}
|
||||||
|
if keycode.pressed(KeyCode::Right) {
|
||||||
|
player.translation.x -= speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if keycode.pressed(KeyCode::Up) {
|
||||||
|
player.translation.z += speed;
|
||||||
|
}
|
||||||
|
if keycode.pressed(KeyCode::Down) {
|
||||||
|
player.translation.z -= speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// collision tests
|
||||||
|
pub fn test_collision_events(
|
||||||
|
mut collision_events: EventReader<CollisionEvent>,
|
||||||
|
mut contact_force_events: EventReader<ContactForceEvent>,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
|
||||||
|
for collision_event in collision_events.iter() {
|
||||||
|
println!("collision");
|
||||||
|
match collision_event {
|
||||||
|
CollisionEvent::Started(entity1, entity2 ,_) => {
|
||||||
|
println!("collision started")
|
||||||
|
}
|
||||||
|
CollisionEvent::Stopped(entity1, entity2 ,_) => {
|
||||||
|
println!("collision ended")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for contact_force_event in contact_force_events.iter() {
|
||||||
|
println!("Received contact force event: {:?}", contact_force_event);
|
||||||
|
}
|
||||||
|
}
|
10
src/physics/controls.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use bevy::prelude::ResMut;
|
||||||
|
use bevy_rapier3d::prelude::RapierConfiguration;
|
||||||
|
|
||||||
|
pub fn pause_physics(mut physics_config: ResMut<RapierConfiguration>){
|
||||||
|
physics_config.physics_pipeline_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resume_physics(mut physics_config: ResMut<RapierConfiguration>){
|
||||||
|
physics_config.physics_pipeline_active = true;
|
||||||
|
}
|
31
src/physics/mod.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
pub mod physics_replace_proxies;
|
||||||
|
pub use physics_replace_proxies::*;
|
||||||
|
|
||||||
|
pub mod utils;
|
||||||
|
pub(crate) use utils::*;
|
||||||
|
|
||||||
|
pub mod controls;
|
||||||
|
pub use controls::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
// use crate::state::{GameState};
|
||||||
|
use super::Collider;
|
||||||
|
pub struct PhysicsPlugin;
|
||||||
|
impl Plugin for PhysicsPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
.register_type::<AutoAABBCollider>()
|
||||||
|
.register_type::<physics_replace_proxies::Collider>()
|
||||||
|
.register_type::<physics_replace_proxies::RigidBodyProxy>()
|
||||||
|
|
||||||
|
// find a way to make serde's stuff serializable
|
||||||
|
// .register_type::<bevy_rapier3d::dynamics::CoefficientCombineRule>()
|
||||||
|
//bevy_rapier3d::dynamics::CoefficientCombineRule
|
||||||
|
|
||||||
|
.add_systems(Update, physics_replace_proxies)
|
||||||
|
//.add_system(pause_physics.in_schedule(OnEnter(GameState::InMenu)))
|
||||||
|
//.add_system(resume_physics.in_schedule(OnEnter(GameState::InGame)))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
126
src/physics/physics_replace_proxies.rs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::primitives::Aabb;
|
||||||
|
use bevy_rapier3d::geometry::Collider as RapierCollider;
|
||||||
|
use bevy_rapier3d::dynamics::RigidBody as RapierRigidBody;
|
||||||
|
use bevy_rapier3d::prelude::{ComputedColliderShape, ActiveEvents, ActiveCollisionTypes};
|
||||||
|
|
||||||
|
use super::utils::*;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, )]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum RigidBodyProxy{
|
||||||
|
#[default]
|
||||||
|
Dynamic,
|
||||||
|
Fixed,
|
||||||
|
Position,
|
||||||
|
Velocity
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, )]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum Collider {
|
||||||
|
Ball(f32),
|
||||||
|
Cuboid(Vec3),
|
||||||
|
Capsule(Vec3, Vec3, f32),
|
||||||
|
#[default]
|
||||||
|
Mesh,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, )]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum AutoAABBCollider {
|
||||||
|
#[default]
|
||||||
|
Cuboid,
|
||||||
|
Ball,
|
||||||
|
Capsule
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaces all physics stand-ins with the actual rapier types
|
||||||
|
pub fn physics_replace_proxies (
|
||||||
|
meshes: Res<Assets<Mesh>>,
|
||||||
|
mesh_handles: Query<&Handle<Mesh>>,
|
||||||
|
// rigidbodies
|
||||||
|
proxy_rigidbodies: Query<(Entity, &RigidBodyProxy,), (Without<RapierRigidBody>, Added<RigidBodyProxy>)>,
|
||||||
|
mut proxy_colliders: Query<(Entity, &Collider, &Name, &mut Visibility), (Without<RapierCollider>, Added<Collider>)>,
|
||||||
|
// needed for tri meshes
|
||||||
|
children: Query<&Children>,
|
||||||
|
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for proxy_colider in proxy_colliders.iter_mut() {
|
||||||
|
let (entity, collider_proxy, name, mut visibility) = proxy_colider;
|
||||||
|
// we hide the collider meshes: perhaps they should be removed altogether once processed ?
|
||||||
|
if name.ends_with( "_collider" ) || name.ends_with( "_sensor" ) {
|
||||||
|
*visibility = Visibility::Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rapier_collider:RapierCollider;
|
||||||
|
match collider_proxy{
|
||||||
|
Collider::Ball(radius) => {
|
||||||
|
println!("proxy: ball");
|
||||||
|
rapier_collider = RapierCollider::ball(*radius);
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
Collider::Cuboid(size) => {
|
||||||
|
println!("proxy: cuboid");
|
||||||
|
rapier_collider = RapierCollider::cuboid(size.x, size.y, size.z);
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
Collider::Capsule(a, b, radius) => {
|
||||||
|
println!("proxy: capsule");
|
||||||
|
rapier_collider = RapierCollider::capsule(*a, *b, *radius);
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
Collider::Mesh => {
|
||||||
|
println!("proxy: mesh");
|
||||||
|
for (_, collider_mesh) in Mesh::search_in_children(entity, &children, &meshes, &mesh_handles)
|
||||||
|
{
|
||||||
|
rapier_collider = RapierCollider::from_bevy_mesh(collider_mesh, &ComputedColliderShape::TriMesh).unwrap();
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
.insert(ActiveCollisionTypes::default() | ActiveCollisionTypes::KINEMATIC_STATIC | ActiveCollisionTypes::STATIC_STATIC | ActiveCollisionTypes::DYNAMIC_STATIC)
|
||||||
|
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS)
|
||||||
|
;
|
||||||
|
// .insert(ActiveEvents::COLLISION_EVENTS)
|
||||||
|
break;
|
||||||
|
// RapierCollider::convex_hull(points)
|
||||||
|
/*commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// rigidbodies
|
||||||
|
for (entity, proxy_rigidbody) in proxy_rigidbodies.iter() {
|
||||||
|
info!("Proxy rigid body !! {:?}", proxy_rigidbody );
|
||||||
|
let rigid_body:RapierRigidBody = match proxy_rigidbody {
|
||||||
|
RigidBodyProxy::Dynamic => RapierRigidBody::Dynamic,
|
||||||
|
RigidBodyProxy::Fixed=> RapierRigidBody::Fixed,
|
||||||
|
RigidBodyProxy::Position => RapierRigidBody::KinematicPositionBased,
|
||||||
|
RigidBodyProxy::Velocity => RapierRigidBody::KinematicVelocityBased,
|
||||||
|
};
|
||||||
|
println!("inserting rigidbody {:?}", rigid_body);
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rigid_body)
|
||||||
|
// IMPORTANT ! this allows collisions between dynamic & static(fixed) entities
|
||||||
|
// see https://rapier.rs/docs/user_guides/bevy_plugin/colliders#active-collision-types
|
||||||
|
.insert(ActiveCollisionTypes::default() | ActiveCollisionTypes::KINEMATIC_STATIC | ActiveCollisionTypes::STATIC_STATIC | ActiveCollisionTypes::DYNAMIC_STATIC)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
175
src/physics/utils.rs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::mesh::{MeshVertexAttributeId, PrimitiveTopology, VertexAttributeValues};
|
||||||
|
// TAKEN VERBATIB FROM https://github.com/janhohenheim/foxtrot/blob/src/util/trait_extension.rs
|
||||||
|
|
||||||
|
pub(crate) trait Vec3Ext: Copy {
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn split(self, up: Vec3) -> SplitVec3;
|
||||||
|
}
|
||||||
|
impl Vec3Ext for Vec3 {
|
||||||
|
#[inline]
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
self.length_squared() < 1e-5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn split(self, up: Vec3) -> SplitVec3 {
|
||||||
|
let vertical = up * self.dot(up);
|
||||||
|
let horizontal = self - vertical;
|
||||||
|
SplitVec3 {
|
||||||
|
vertical,
|
||||||
|
horizontal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub(crate) struct SplitVec3 {
|
||||||
|
pub(crate) vertical: Vec3,
|
||||||
|
pub(crate) horizontal: Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait Vec2Ext: Copy {
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn x0y(self) -> Vec3;
|
||||||
|
}
|
||||||
|
impl Vec2Ext for Vec2 {
|
||||||
|
#[inline]
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
self.length_squared() < 1e-5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn x0y(self) -> Vec3 {
|
||||||
|
Vec3::new(self.x, 0., self.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait MeshExt {
|
||||||
|
fn transform(&mut self, transform: Transform);
|
||||||
|
fn transformed(&self, transform: Transform) -> Mesh;
|
||||||
|
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]>;
|
||||||
|
fn search_in_children<'a>(
|
||||||
|
parent: Entity,
|
||||||
|
children: &'a Query<&Children>,
|
||||||
|
meshes: &'a Assets<Mesh>,
|
||||||
|
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||||
|
) -> Vec<(Entity, &'a Mesh)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeshExt for Mesh {
|
||||||
|
fn transform(&mut self, transform: Transform) {
|
||||||
|
for coords in self.read_coords_mut(Mesh::ATTRIBUTE_POSITION.clone()) {
|
||||||
|
let vec3 = (*coords).into();
|
||||||
|
let transformed = transform.transform_point(vec3);
|
||||||
|
*coords = transformed.into();
|
||||||
|
}
|
||||||
|
for normal in self.read_coords_mut(Mesh::ATTRIBUTE_NORMAL.clone()) {
|
||||||
|
let vec3 = (*normal).into();
|
||||||
|
let transformed = transform.rotation.mul_vec3(vec3);
|
||||||
|
*normal = transformed.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transformed(&self, transform: Transform) -> Mesh {
|
||||||
|
let mut mesh = self.clone();
|
||||||
|
mesh.transform(transform);
|
||||||
|
mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]> {
|
||||||
|
// Guaranteed by Bevy for the current usage
|
||||||
|
match self
|
||||||
|
.attribute_mut(id)
|
||||||
|
.expect("Failed to read unknown mesh attribute")
|
||||||
|
{
|
||||||
|
VertexAttributeValues::Float32x3(values) => values,
|
||||||
|
// Guaranteed by Bevy for the current usage
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_in_children<'a>(
|
||||||
|
parent: Entity,
|
||||||
|
children_query: &'a Query<&Children>,
|
||||||
|
meshes: &'a Assets<Mesh>,
|
||||||
|
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||||
|
) -> Vec<(Entity, &'a Mesh)> {
|
||||||
|
if let Ok(children) = children_query.get(parent) {
|
||||||
|
let mut result: Vec<_> = children
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entity| mesh_handles.get(*entity).ok().map(|mesh| (*entity, mesh)))
|
||||||
|
.map(|(entity, mesh_handle)| {
|
||||||
|
(
|
||||||
|
entity,
|
||||||
|
meshes
|
||||||
|
.get(mesh_handle)
|
||||||
|
.expect("Failed to get mesh from handle"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|(entity, mesh)| {
|
||||||
|
assert_eq!(mesh.primitive_topology(), PrimitiveTopology::TriangleList);
|
||||||
|
(entity, mesh)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut inner_result = children
|
||||||
|
.iter()
|
||||||
|
.flat_map(|entity| {
|
||||||
|
Self::search_in_children(*entity, children_query, meshes, mesh_handles)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
result.append(&mut inner_result);
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait F32Ext: Copy {
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn squared(self) -> f32;
|
||||||
|
fn lerp(self, other: f32, ratio: f32) -> f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl F32Ext for f32 {
|
||||||
|
#[inline]
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
self.abs() < 1e-5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn squared(self) -> f32 {
|
||||||
|
self * self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn lerp(self, other: f32, ratio: f32) -> f32 {
|
||||||
|
self.mul_add(1. - ratio, other * ratio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait TransformExt: Copy {
|
||||||
|
fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform;
|
||||||
|
fn lerp(self, other: Transform, ratio: f32) -> Transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransformExt for Transform {
|
||||||
|
fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform {
|
||||||
|
let direction = target - self.translation;
|
||||||
|
let horizontal_direction = direction - up * direction.dot(up);
|
||||||
|
let look_target = self.translation + horizontal_direction;
|
||||||
|
self.looking_at(look_target, up)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lerp(self, other: Transform, ratio: f32) -> Transform {
|
||||||
|
let translation = self.translation.lerp(other.translation, ratio);
|
||||||
|
let rotation = self.rotation.slerp(other.rotation, ratio);
|
||||||
|
let scale = self.scale.lerp(other.scale, ratio);
|
||||||
|
Transform {
|
||||||
|
translation,
|
||||||
|
rotation,
|
||||||
|
scale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
src/physics/utils_old.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::mesh::{MeshVertexAttributeId, PrimitiveTopology, VertexAttributeValues};
|
||||||
|
// TAKEN VERBATIB FROM https://github.com/janhohenheim/foxtrot/blob/6e31fc02652fc9d085a4adde0a73ab007dbbb0dc/src/util/trait_extension.rs
|
||||||
|
|
||||||
|
pub trait Vec3Ext {
|
||||||
|
#[allow(clippy::wrong_self_convention)] // Because [`Vec3`] is [`Copy`]
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn x0z(self) -> Vec3;
|
||||||
|
}
|
||||||
|
impl Vec3Ext for Vec3 {
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
[self.x, self.y, self.z].iter().all(|&x| x.abs() < 1e-5)
|
||||||
|
}
|
||||||
|
fn x0z(self) -> Vec3 {
|
||||||
|
Vec3::new(self.x, 0., self.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MeshExt {
|
||||||
|
fn transform(&mut self, transform: Transform);
|
||||||
|
fn transformed(&self, transform: Transform) -> Mesh;
|
||||||
|
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]>;
|
||||||
|
fn search_in_children<'a>(
|
||||||
|
children: &'a Children,
|
||||||
|
meshes: &'a Assets<Mesh>,
|
||||||
|
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||||
|
) -> (Entity, &'a Mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeshExt for Mesh {
|
||||||
|
fn transform(&mut self, transform: Transform) {
|
||||||
|
for attribute in [Mesh::ATTRIBUTE_POSITION, Mesh::ATTRIBUTE_NORMAL] {
|
||||||
|
for coords in self.read_coords_mut(attribute.clone()) {
|
||||||
|
let vec3 = (*coords).into();
|
||||||
|
let transformed = transform.transform_point(vec3);
|
||||||
|
*coords = transformed.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transformed(&self, transform: Transform) -> Mesh {
|
||||||
|
let mut mesh = self.clone();
|
||||||
|
mesh.transform(transform);
|
||||||
|
mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]> {
|
||||||
|
match self.attribute_mut(id).unwrap() {
|
||||||
|
VertexAttributeValues::Float32x3(values) => values,
|
||||||
|
// Guaranteed by Bevy
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_in_children<'a>(
|
||||||
|
children: &'a Children,
|
||||||
|
meshes: &'a Assets<Mesh>,
|
||||||
|
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||||
|
) -> (Entity, &'a Mesh) {
|
||||||
|
let entity_handles: Vec<_> = children
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entity| mesh_handles.get(*entity).ok().map(|mesh| (*entity, mesh)))
|
||||||
|
.collect();
|
||||||
|
assert_eq!(
|
||||||
|
entity_handles.len(),
|
||||||
|
1,
|
||||||
|
"Collider must contain exactly one mesh, but found {}",
|
||||||
|
entity_handles.len()
|
||||||
|
);
|
||||||
|
let (entity, mesh_handle) = entity_handles.first().unwrap();
|
||||||
|
let mesh = meshes.get(mesh_handle).unwrap();
|
||||||
|
assert_eq!(mesh.primitive_topology(), PrimitiveTopology::TriangleList);
|
||||||
|
(*entity, mesh)
|
||||||
|
}
|
||||||
|
}
|
179
src/process_gltf/gltf_to_components.rs
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use core::ops::Deref;
|
||||||
|
|
||||||
|
use serde_json::Value;
|
||||||
|
use serde::de::DeserializeSeed;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::reflect::serde::{UntypedReflectDeserializer, ReflectSerializer};
|
||||||
|
use bevy::reflect::TypeRegistryInternal;
|
||||||
|
use bevy::gltf::{Gltf, GltfExtras};
|
||||||
|
|
||||||
|
use crate::Player;
|
||||||
|
use crate::camera::{CameraTracking, CameraTrackingOffset};
|
||||||
|
|
||||||
|
use super::capitalize_first_letter;
|
||||||
|
|
||||||
|
pub fn gltf_extras_to_components(
|
||||||
|
gltf: &mut Gltf,
|
||||||
|
scenes: &mut ResMut<Assets<Scene>>,
|
||||||
|
type_registry: impl Deref<Target = TypeRegistryInternal>,
|
||||||
|
gltf_name: &str
|
||||||
|
){
|
||||||
|
let mut added_components = 0;
|
||||||
|
for (_name, scene) in &gltf.named_scenes {
|
||||||
|
info!("gltf: {:?} scene name {:?}", gltf_name, _name);
|
||||||
|
|
||||||
|
let scene = scenes.get_mut(scene).unwrap();
|
||||||
|
|
||||||
|
let mut query = scene.world.query::<(Entity, &Name, &GltfExtras, &Parent)>();
|
||||||
|
let mut entity_components: HashMap<Entity, Vec<Box<dyn Reflect>> > = HashMap::new();
|
||||||
|
for (entity, name, extras, parent) in query.iter(&scene.world) {
|
||||||
|
debug!("Name: {}, entity {:?}, parent: {:?}", name, entity, parent);
|
||||||
|
let reflect_components = ronstring_to_reflect_component(&extras.value, &type_registry);
|
||||||
|
added_components = reflect_components.len();
|
||||||
|
debug!("Found components {}", added_components);
|
||||||
|
|
||||||
|
// we assign the components specified in entity_data/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>> = Vec::new();
|
||||||
|
let current_components = &entity_components[&target_entity];
|
||||||
|
|
||||||
|
// first inject the current components
|
||||||
|
for component in current_components {
|
||||||
|
updated_components.push(component.clone_value());
|
||||||
|
}
|
||||||
|
// then inject the new components: this also enables overwrite components set in the collection
|
||||||
|
for component in reflect_components {
|
||||||
|
updated_components.push(component.clone_value());
|
||||||
|
}
|
||||||
|
entity_components.insert(target_entity, updated_components);
|
||||||
|
|
||||||
|
|
||||||
|
}else {
|
||||||
|
entity_components.insert(target_entity, reflect_components);
|
||||||
|
}
|
||||||
|
// shorthand, did not manage to get it working
|
||||||
|
/* entity_components.insert(
|
||||||
|
target_entity,
|
||||||
|
if entity_components.contains_key(&target_entity) {
|
||||||
|
entity_components[&target_entity].push(reflect_components) } else { reflect_components }
|
||||||
|
);*/
|
||||||
|
|
||||||
|
debug!("-----value {:?}", &extras.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// println!("FOUND ASSET {:?}", foob);
|
||||||
|
// GltfNode
|
||||||
|
// find a way to link this name to the current entity ? => WOULD BE VERY USEFULL for animations & co !!
|
||||||
|
debug!("done pre-processing components, now adding them to entities");
|
||||||
|
for (entity, components) in entity_components {
|
||||||
|
if components.len() > 0 {
|
||||||
|
debug!("--entity {:?}, components {}", entity, components.len());
|
||||||
|
}
|
||||||
|
for component in components {
|
||||||
|
let mut entity_mut = scene.world.entity_mut(entity);
|
||||||
|
debug!("------adding {} {:?}", component.type_name(), component);
|
||||||
|
|
||||||
|
type_registry
|
||||||
|
.get_with_name(component.type_name())
|
||||||
|
.unwrap() // Component was successfully deserialized, it has to be in the registry
|
||||||
|
.data::<ReflectComponent>()
|
||||||
|
.unwrap() // Hopefully, the component deserializer ensures those are components
|
||||||
|
.insert(&mut entity_mut, &*component)
|
||||||
|
;
|
||||||
|
|
||||||
|
// info!("all components {:?}", scene.world.entity(entity).archetype().components());
|
||||||
|
// scene.world.components().
|
||||||
|
// TODO: how can we insert any additional components "by hand" here ?
|
||||||
|
}
|
||||||
|
let e_mut = scene.world.entity_mut(entity);
|
||||||
|
let archetype = e_mut.archetype().clone();//.components();
|
||||||
|
let _all_components = archetype.components();
|
||||||
|
// println!("All components {:?}", all_components);
|
||||||
|
|
||||||
|
if added_components > 0 {
|
||||||
|
debug!("------done adding {} components", added_components);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!("done extracting gltf_extras /n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn ronstring_to_reflect_component(
|
||||||
|
ron_string: &String,
|
||||||
|
type_registry: &TypeRegistryInternal
|
||||||
|
) -> Vec<Box<dyn Reflect>> {
|
||||||
|
println!("RON string {}", ron_string);
|
||||||
|
let lookup: HashMap<String, Value> = ron::from_str(ron_string.as_str()).unwrap(); //serde_json::from_str(ron_string.as_str()).unwrap();
|
||||||
|
let mut components: Vec<Box<dyn Reflect>> = Vec::new();
|
||||||
|
for (key, value) in lookup.into_iter() {
|
||||||
|
println!("KEY {} , VALUE {}", key, value);
|
||||||
|
let type_string = key.replace("component: ", "").trim().to_string();
|
||||||
|
let capitalized_type_name = capitalize_first_letter(type_string.as_str());
|
||||||
|
println!("capitalized_type_name {}", capitalized_type_name);
|
||||||
|
let mut parsed_value = format!("{}", value);
|
||||||
|
parsed_value = ron::from_str(parsed_value.as_str()).unwrap_or(parsed_value);
|
||||||
|
|
||||||
|
if let Some(type_registration) = type_registry.get_with_short_name(capitalized_type_name.as_str()) {
|
||||||
|
println!("parsed value {}",parsed_value);
|
||||||
|
if parsed_value == "" {
|
||||||
|
parsed_value = "()".to_string();
|
||||||
|
}
|
||||||
|
if parsed_value.starts_with("[") && parsed_value.ends_with("]") {
|
||||||
|
// FIXME/ horrible
|
||||||
|
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
|
||||||
|
if parsed.len() == 3 {
|
||||||
|
let bla = Vec3::from_array([parsed[0], parsed[1], parsed[2]]);
|
||||||
|
println!("TOTO 2222 {:?}", bla);
|
||||||
|
/*let serializer = ReflectSerializer::new(&bla, &type_registry);
|
||||||
|
let serialized = ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();
|
||||||
|
parsed_value = serialized;*/
|
||||||
|
parsed_value = format!("((x:{},y:{},z:{}))", bla.x, bla.y, bla.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ron_string = format!("
|
||||||
|
{{
|
||||||
|
\"{}\":{}
|
||||||
|
}}",
|
||||||
|
type_registration.type_name(),
|
||||||
|
parsed_value
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
let test_struct = CameraTrackingOffset::default();
|
||||||
|
let serializer = ReflectSerializer::new(&test_struct, &type_registry);
|
||||||
|
let serialized =
|
||||||
|
ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();
|
||||||
|
println!("serialized player {}", serialized);*/
|
||||||
|
|
||||||
|
debug!("component data json string {}", ron_string);
|
||||||
|
let mut deserializer = ron::Deserializer::from_str(ron_string.as_str()).unwrap();
|
||||||
|
let reflect_deserializer = UntypedReflectDeserializer::new(&type_registry);
|
||||||
|
let component = reflect_deserializer.deserialize(&mut deserializer).expect(format!("failed to deserialize component {} with value: {}", key, value).as_str());
|
||||||
|
|
||||||
|
components.push(component);
|
||||||
|
println!("found type registration for {}", capitalized_type_name);
|
||||||
|
} else {
|
||||||
|
warn!("no type registration for {}", capitalized_type_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
components
|
||||||
|
}
|
||||||
|
|
34
src/process_gltf/mod.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
pub mod utils;
|
||||||
|
pub use utils::*;
|
||||||
|
|
||||||
|
pub mod gltf_to_components;
|
||||||
|
pub use gltf_to_components::*;
|
||||||
|
|
||||||
|
pub mod process_gltfs;
|
||||||
|
pub use process_gltfs::*;
|
||||||
|
|
||||||
|
// pub mod models_replace_proxies;
|
||||||
|
// pub use models_replace_proxies::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
// use crate::state::{AppState};
|
||||||
|
|
||||||
|
pub struct ProcessGltfPlugin;
|
||||||
|
impl Plugin for ProcessGltfPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
.insert_resource(GltfLoadingTracker::new())
|
||||||
|
|
||||||
|
.add_systems(PreUpdate, (
|
||||||
|
track_new_gltf,
|
||||||
|
process_loaded_scenes,
|
||||||
|
))
|
||||||
|
|
||||||
|
// .add_systems((models_replace_proxies,).in_set(OnUpdate(AppState::GameRunning)))
|
||||||
|
|
||||||
|
// compute the aabbs of a whole hierarchy
|
||||||
|
//.add_systems((compute_compound_aabb,).in_set(OnUpdate(AppState::GameRunning)))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
79
src/process_gltf/process_gltfs.rs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use bevy::{prelude::*, asset::LoadState};
|
||||||
|
use bevy::gltf::Gltf;
|
||||||
|
|
||||||
|
use super::gltf_extras_to_components;
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct GltfLoadingTracker{
|
||||||
|
pub loading_gltfs: HashSet<Handle<Gltf>>,
|
||||||
|
pub loaded_gltfs: HashSet<Handle<Gltf>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GltfLoadingTracker {
|
||||||
|
pub fn new() -> GltfLoadingTracker {
|
||||||
|
GltfLoadingTracker {
|
||||||
|
loaded_gltfs : HashSet::new(),
|
||||||
|
loading_gltfs: HashSet::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn add_scene(&mut self, handle: Handle<Gltf>) {
|
||||||
|
self.loading_gltfs.insert(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn track_new_gltf(
|
||||||
|
mut tracker: ResMut<GltfLoadingTracker>,
|
||||||
|
mut events: EventReader<AssetEvent<Gltf>>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
if let AssetEvent::Created { handle } = event {
|
||||||
|
tracker.add_scene(handle.clone());
|
||||||
|
debug!("gltf created {:?}", handle.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_loaded_scenes(
|
||||||
|
mut gltfs: ResMut<Assets<Gltf>>,
|
||||||
|
mut scenes: ResMut<Assets<Scene>>,
|
||||||
|
mut tracker: ResMut<GltfLoadingTracker>,
|
||||||
|
app_type_registry: Res<AppTypeRegistry>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
|
||||||
|
) {
|
||||||
|
|
||||||
|
let mut loaded_gltfs = Vec::new();
|
||||||
|
for gltf in &tracker.loading_gltfs {
|
||||||
|
info!("checking for loaded gltfs {:?}", asset_server.get_load_state(gltf));
|
||||||
|
|
||||||
|
if asset_server.get_load_state(gltf.clone()) == LoadState::Loaded {
|
||||||
|
debug!("Adding scene to processing list");
|
||||||
|
loaded_gltfs.push(gltf.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let type_registry = app_type_registry.read();
|
||||||
|
|
||||||
|
for gltf_handle in &loaded_gltfs {
|
||||||
|
if let Some(gltf) = gltfs.get_mut(gltf_handle) {
|
||||||
|
|
||||||
|
// TODO this is a temporary workaround for library management
|
||||||
|
if let Some(asset_path) = asset_server.get_handle_path(gltf_handle) {
|
||||||
|
let gltf_name = asset_path.path().file_stem().unwrap().to_str().unwrap();
|
||||||
|
gltf_extras_to_components(gltf, &mut scenes, &*type_registry, &gltf_name);
|
||||||
|
//gltf_extras_to_prefab_infos(gltf, &mut scenes, &*type_registry, &gltf_name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gltf_extras_to_components(gltf, &mut scenes, &*type_registry, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tracker.loading_gltfs.remove(gltf_handle);
|
||||||
|
tracker.loaded_gltfs.insert(gltf_handle.clone());
|
||||||
|
debug!("Done loading scene");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
4
src/process_gltf/utils.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub fn capitalize_first_letter(s: &str) -> String {
|
||||||
|
s[0..1].to_uppercase() + &s[1..]
|
||||||
|
}
|
||||||
|
|
12
src/relationships/mod.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
pub mod relationships_insert_dependant_components;
|
||||||
|
pub use relationships_insert_dependant_components::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct EcsRelationshipsPlugin;
|
||||||
|
impl Plugin for EcsRelationshipsPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub fn insert_dependant_component<Dependant: Component, Dependency: Component+ std::default::Default>(
|
||||||
|
mut commands: Commands,
|
||||||
|
entities_without_depency: Query<(Entity, &Name), (With<Dependant>, Without<Dependency>)>,
|
||||||
|
) {
|
||||||
|
for (entity, name) in entities_without_depency.iter() {
|
||||||
|
let name = name.clone().to_string();
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(
|
||||||
|
Dependency::default()
|
||||||
|
)
|
||||||
|
;
|
||||||
|
warn!("found an entity called {} with a {} component but without an {}, please check your assets", name.clone(), std::any::type_name::<Dependant>(), std::any::type_name::<Dependency>());
|
||||||
|
}
|
||||||
|
}
|