feat(scene components) : added scene components to control ambient, bloom etc from Blender(#76)

* feat(tools):  scene component injection & export
 - ambient light, shadowmap resolution, bloom intensity, ambient occlusion are now directly settable from scene components that are injected automatically in the scene 
 - relaxed requirements for changes(aka when to export gltf) for main scenes,
so that render , background etc changes also trigger a save
 - added scene components examples
 - added additional proxies &  handling for camera & lights
 - in relevant cases, made sure that entity specific components are not overridden by scene components
 - added option to toggle scene level exports of params/ components
 - ambient colour/ intensity is now also set as background/clear colour
  - updated docs
This commit is contained in:
Mark Moissette 2023-12-17 15:31:25 +01:00 committed by GitHub
parent 3d192f7876
commit 0aead0853c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 8172 additions and 2 deletions

12
Cargo.lock generated
View File

@ -809,6 +809,18 @@ dependencies = [
"rand",
]
[[package]]
name = "bevy_gltf_blueprints_basic_scene_components_example"
version = "0.3.0"
dependencies = [
"bevy",
"bevy_asset_loader",
"bevy_editor_pls",
"bevy_gltf_blueprints",
"bevy_rapier3d",
"rand",
]
[[package]]
name = "bevy_gltf_blueprints_basic_xpbd_physics_example"
version = "0.3.0"

View File

@ -4,6 +4,7 @@ members = [
"crates/bevy_gltf_blueprints",
"examples/bevy_gltf_components/basic/",
"examples/bevy_gltf_blueprints/basic/",
"examples/bevy_gltf_blueprints/basic_scene_components/",
"examples/bevy_gltf_blueprints/basic_xpbd_physics/",
"examples/bevy_gltf_blueprints/animation/",
"examples/bevy_gltf_blueprints/multiple_levels/",

View File

@ -273,6 +273,8 @@ https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/example
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/basic_xpbd_physics
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/basic_scene_components
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/animation
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/multiple_levels

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
[package]
name = "bevy_gltf_blueprints_basic_scene_components_example"
version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy="0.12"
bevy_gltf_blueprints = { path = "../../../crates/bevy_gltf_blueprints" }
bevy_rapier3d = { version = "0.23.0", features = [ "serde-serialize", "debug-render-3d", "enhanced-determinism"] }
bevy_asset_loader = { version = "0.18", features = ["standard_dynamic_assets" ]}
bevy_editor_pls = { version = "0.6" }
rand = "0.8.5"

View File

@ -0,0 +1,11 @@
# Basic scene components demo
This example showcases the use of "scene" components ie components that are injected into the root scene/level so that you can control things
like ambient lighting, bloom, shadowmap resolution, & ao directly from Blender.
## Running this example
```
cargo run --features bevy/dynamic_linking
```

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,485 @@
(
resources: {},
entities: {
20: (
components: {
"bevy_render::camera::projection::Projection": Perspective((
fov: 0.3995965,
aspect_ratio: 1.7777778,
near: 0.1,
far: 100.0,
)),
"bevy_render::primitives::Frustum": (),
"bevy_transform::components::transform::Transform": (
translation: (
x: 34.821884,
y: 49.024857,
z: -36.79615,
),
rotation: (-0.1694689, 0.82838506, 0.40884802, 0.3433684),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core_pipeline::tonemapping::Tonemapping": BlenderFilmic,
"bevy_core_pipeline::tonemapping::DebandDither": Enabled,
"bevy_render::view::ColorGrading": (
exposure: 0.0,
gamma: 1.0,
pre_saturation: 1.0,
post_saturation: 1.0,
),
"bevy_core::name::Name": (
hash: 17702508670109176045,
name: "Camera",
),
"advanced::core::camera::camera_tracking::CameraTrackingOffset": ((
x: 26.0,
y: 48.0,
z: -26.0,
)),
"bevy_pbr::light::ClusterConfig": FixedZ(
total: 4096,
z_slices: 24,
z_config: (
first_slice_depth: 5.0,
far_z_mode: MaxLightRange,
),
dynamic_resizing: true,
),
"bevy_core_pipeline::bloom::settings::BloomSettings": (
intensity: 0.01,
low_frequency_boost: 0.7,
low_frequency_boost_curvature: 0.95,
high_pass_frequency: 1.0,
prefilter_settings: (
threshold: 0.0,
threshold_softness: 0.0,
),
composite_mode: Additive,
),
},
),
34: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: 4.697565,
y: 1.5983224,
z: 8.962274,
),
rotation: (0.000000000000000031724054, -0.00000000000000000000647681, -0.000013119204, 1.0),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 9837288155836662016,
name: "Health_Pickup.001",
),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"),
"advanced::game::picking::Pickable": (),
},
),
54: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: 8.799996,
y: 1.02484,
z: -10.799994,
),
rotation: (0.0, 0.0, 0.0, 1.0),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 17978181434632022651,
name: "Player",
),
"advanced::core::camera::camera_tracking::CameraTrackable": (),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Player"),
"advanced::game::Player": (),
"advanced::game::SoundMaterial": Wood,
},
),
60: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: 3.6351967,
y: 1.7298106,
z: -7.313273,
),
rotation: (0.0, 0.0, 0.0, 1.0),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 7225506896223411979,
name: "MagicTeapot.001",
),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("MagicTeapot"),
},
),
64: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: -4.6068983,
y: 1.5983224,
z: -10.579347,
),
rotation: (0.000000000000000031724054, 0.00000000000000000000647681, 0.000013119204, 1.0),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 3089896164553476909,
name: "Health_Pickup.002",
),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"),
"advanced::game::picking::Pickable": (),
},
),
72: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: -11.560788,
y: 0.0,
z: 7.6554174,
),
rotation: (0.0, 0.0, 0.0, 1.0),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 16961132108296874979,
name: "Container.001",
),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"),
"advanced::game::picking::Pickable": (),
},
),
80: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: -21.397858,
y: 0.3833189,
z: -0.32418346,
),
rotation: (0.0, 0.0, 0.0, 1.0),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 5104740624378885265,
name: "Container.002",
),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"),
"advanced::game::picking::Pickable": (),
},
),
82: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: 2.9156065,
y: 1.4984571,
z: 2.1909573,
),
rotation: (0.058853183, 0.0726243, 0.2048649, 0.97431636),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 107557640935939866,
name: "test5159735758431545549",
),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"),
"advanced::game::picking::Pickable": (),
"bevy_rapier3d::dynamics::rigid_body::Velocity": (
linvel: (
x: -1.2580805,
y: -0.39687577,
z: 0.4816798,
),
angvel: (
x: 0.2979751,
y: 0.07926611,
z: 0.8434645,
),
),
},
),
86: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: 0.26087752,
y: 1.5525806,
z: 1.5980839,
),
rotation: (0.059497803, -0.0000018232388, 0.13145457, 0.9895351),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 3398656236303073559,
name: "test7470642598731063943",
),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"),
"advanced::game::picking::Pickable": (),
"bevy_rapier3d::dynamics::rigid_body::Velocity": (
linvel: (
x: -0.9268077,
y: -0.19806683,
z: 0.41948256,
),
angvel: (
x: 0.26946256,
y: -0.000006710977,
z: 0.5953494,
),
),
},
),
90: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: 2.6515265,
y: 1.5944021,
z: -4.391837,
),
rotation: (-0.030030435, -0.0000006527225, 0.029748484, 0.9991062),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 12541900054595385134,
name: "test3938024405863834719",
),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"),
"advanced::game::picking::Pickable": (),
"bevy_rapier3d::dynamics::rigid_body::Velocity": (
linvel: (
x: -0.28430828,
y: -0.022357654,
z: -0.2870027,
),
angvel: (
x: -0.17986917,
y: -0.0000035613396,
z: 0.17818078,
),
),
},
),
94: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: -4.2356462,
y: 1.596993,
z: 0.7254991,
),
rotation: (-0.0221751, -0.0000000001891749, 0.011065631, 0.99969286),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 6757906322211730861,
name: "test11007490954016878479",
),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"),
"advanced::game::picking::Pickable": (),
"bevy_rapier3d::dynamics::rigid_body::Velocity": (
linvel: (
x: -0.21747473,
y: -0.014912919,
z: -0.43581253,
),
angvel: (
x: -0.2727097,
y: -0.0000000034594905,
z: 0.13608481,
),
),
},
),
98: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: 3.1525247,
y: 1.5518407,
z: -2.9611976,
),
rotation: (-0.09219627, 0.1602262, -0.11205085, 0.9763565),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 12588565107899185946,
name: "test5980867849331267699",
),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"),
"advanced::game::picking::Pickable": (),
"bevy_rapier3d::dynamics::rigid_body::Velocity": (
linvel: (
x: 0.8323179,
y: -0.20597076,
z: -0.68975484,
),
angvel: (
x: -0.37971017,
y: 0.49603412,
z: -0.6079359,
),
),
},
),
4294967310: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: 4.826278,
y: 1.2710563,
z: -3.1997645,
),
rotation: (-0.303028, 0.00000087800436, -0.23889118, 0.9225535),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 15533546218717453536,
name: "test12380979123759326444",
),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"),
"advanced::game::picking::Pickable": (),
"bevy_rapier3d::dynamics::rigid_body::Velocity": (
linvel: (
x: 1.2146912,
y: -1.1640646,
z: -1.5408095,
),
angvel: (
x: -1.1932359,
y: 0.000002945365,
z: -0.94068503,
),
),
},
),
4294967314: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: 3.9906094,
y: 1.4824095,
z: 2.4394412,
),
rotation: (0.06015042, 0.085218765, 0.2215642, 0.9695509),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 2466794778849297109,
name: "test12475628281920299197",
),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"),
"advanced::game::picking::Pickable": (),
"bevy_rapier3d::dynamics::rigid_body::Velocity": (
linvel: (
x: -1.0818624,
y: -0.37798148,
z: 0.45334253,
),
angvel: (
x: 0.25961447,
y: 0.14854014,
z: 0.7426717,
),
),
},
),
4294967321: (
components: {
"bevy_transform::components::transform::Transform": (
translation: (
x: 2.2306876,
y: 0.989814,
z: -1.3596333,
),
rotation: (0.30614096, 0.002587511, -0.42789298, 0.8503991),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_core::name::Name": (
hash: 1545925632270385398,
name: "test15780367212768138828",
),
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"),
"advanced::game::picking::Pickable": (),
"bevy_rapier3d::dynamics::rigid_body::Velocity": (
linvel: (
x: 1.3027526,
y: -1.8947054,
z: 1.6179247,
),
angvel: (
x: 1.4565696,
y: -0.16299045,
z: -1.3631926,
),
),
},
),
},
)

View File

@ -0,0 +1,5 @@
use bevy::prelude::*;
use bevy_asset_loader::prelude::*;
#[derive(AssetCollection, Resource)]
pub struct CoreAssets {}

View File

@ -0,0 +1,13 @@
use bevy::gltf::Gltf;
use bevy::prelude::*;
use bevy::utils::HashMap;
use bevy_asset_loader::prelude::*;
#[derive(AssetCollection, Resource)]
pub struct GameAssets {
#[asset(key = "world")]
pub world: Handle<Gltf>,
#[asset(key = "models", collection(typed, mapped))]
pub models: HashMap<String, Handle<Gltf>>,
}

View File

@ -0,0 +1,35 @@
pub mod assets_core;
pub use assets_core::*;
pub mod assets_game;
pub use assets_game::*;
use bevy::prelude::*;
use bevy_asset_loader::prelude::*;
use crate::state::AppState;
pub struct AssetsPlugin;
impl Plugin for AssetsPlugin {
fn build(&self, app: &mut App) {
app
// load core assets (ie assets needed in the main menu, and everywhere else before loading more assets in game)
.add_loading_state(
LoadingState::new(AppState::CoreLoading).continue_to_state(AppState::MenuRunning),
)
.add_dynamic_collection_to_loading_state::<_, StandardDynamicAssetCollection>(
AppState::CoreLoading,
"assets_core.assets.ron",
)
.add_collection_to_loading_state::<_, CoreAssets>(AppState::CoreLoading)
// load game assets
.add_loading_state(
LoadingState::new(AppState::AppLoading).continue_to_state(AppState::AppRunning),
)
.add_dynamic_collection_to_loading_state::<_, StandardDynamicAssetCollection>(
AppState::AppLoading,
"assets_game.assets.ron",
)
.add_collection_to_loading_state::<_, GameAssets>(AppState::AppLoading);
}
}

View File

@ -0,0 +1,53 @@
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
use bevy::core_pipeline::experimental::taa::TemporalAntiAliasBundle;
use bevy::core_pipeline::tonemapping::{DebandDither, Tonemapping};
use bevy::pbr::ScreenSpaceAmbientOcclusionBundle;
use bevy::prelude::*;
use super::CameraTrackingOffset;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct SSAOSettings;
pub fn camera_replace_proxies(
mut commands: Commands,
mut added_cameras: Query<(Entity, &mut Camera, Option<&BloomSettings>, Option<&SSAOSettings>), (Added<Camera>, With<CameraTrackingOffset>)>,
added_bloom_settings : Query<&BloomSettings, Added<BloomSettings>>,
added_ssao_settings: Query<&SSAOSettings, Added<SSAOSettings>>, // Move to camera
) {
for (entity, mut camera, bloom_settings, ssao_setting) in added_cameras.iter_mut() {
info!("detected added camera, updating proxy");
camera.hdr = true;
commands
.entity(entity)
.insert(DebandDither::Enabled)
.insert(Tonemapping::BlenderFilmic)
;
// we only inject the scene_level bloom settings if there are no settings already on the Camera
if bloom_settings.is_none() {
for bloom_settings in added_bloom_settings.iter(){
commands
.entity(entity)
.insert(BloomSettings {
intensity: bloom_settings.intensity,
composite_mode: BloomCompositeMode::Additive,
..default()
});
}
}
if ssao_setting.is_none() {
for _ in added_ssao_settings.iter(){
commands.insert_resource(Msaa::Off); // when using SSAO, you cannot use Msaa
commands.entity(entity)
.insert(ScreenSpaceAmbientOcclusionBundle::default())
.insert(TemporalAntiAliasBundle::default());
}
}
}
}

View 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 {
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);
}
}
}

View 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::*;
use bevy_gltf_blueprints::GltfBlueprintsSet;
pub struct CameraPlugin;
impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
app.register_type::<CameraTrackable>()
.register_type::<CameraTracking>()
.register_type::<CameraTrackingOffset>()
.register_type::<SSAOSettings>()
.add_systems(
Update,
(
camera_replace_proxies.after(GltfBlueprintsSet::AfterSpawn),
camera_track,
),
);
}
}

View File

@ -0,0 +1,59 @@
use bevy::prelude::*;
use bevy::pbr::{CascadeShadowConfig, CascadeShadowConfigBuilder, DirectionalLightShadowMap};
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct AmbientLightSettings {pub color: Color, pub brightness: f32}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct ShadowmapSettings {pub size: usize}
pub fn lighting_replace_proxies(
mut added_dirights: Query<(Entity, &mut DirectionalLight), Added<DirectionalLight>>,
mut added_spotlights: Query<&mut SpotLight, Added<SpotLight>>,
mut added_pointlights: Query<&mut PointLight, Added<PointLight>>,
added_ambient_proxies : Query<&AmbientLightSettings, Added<AmbientLightSettings>>,
added_shadowmap_settings : Query<&ShadowmapSettings, Added<ShadowmapSettings>>,
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;
}
for mut light in added_pointlights.iter_mut() {
light.intensity *= 0.001; // arbitrary/ eyeballed to match the levels of Blender
light.shadows_enabled = true;
}
for setting in added_shadowmap_settings.iter() {
commands.insert_resource(DirectionalLightShadowMap { size: setting.size });
}
for ambient in added_ambient_proxies.iter(){
commands.insert_resource(AmbientLight {
color: ambient.color,
brightness: ambient.brightness,
});
// FIXME: does this belong here ?
commands.insert_resource(ClearColor(ambient.color * ambient.brightness));
}
}

View File

@ -0,0 +1,19 @@
mod lighting_replace_proxies;
use lighting_replace_proxies::*;
use bevy::pbr::NotShadowCaster;
use bevy::prelude::*;
pub struct LightingPlugin;
impl Plugin for LightingPlugin {
fn build(&self, app: &mut App) {
app
.register_type::<AmbientLightSettings>()
.register_type::<ShadowmapSettings>()
// FIXME: adding these since they are missing
.register_type::<NotShadowCaster>()
.add_systems(PreUpdate, lighting_replace_proxies)
;
}
}

View File

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

View File

@ -0,0 +1,25 @@
use bevy::{
ecs::system::Res,
input::{keyboard::KeyCode, Input},
prelude::{info, ResMut},
};
use bevy_rapier3d::{prelude::RapierConfiguration, render::DebugRenderContext};
pub fn pause_physics(mut physics_config: ResMut<RapierConfiguration>) {
info!("pausing physics");
physics_config.physics_pipeline_active = false;
}
pub fn resume_physics(mut physics_config: ResMut<RapierConfiguration>) {
info!("unpausing physics");
physics_config.physics_pipeline_active = true;
}
pub fn toggle_physics_debug(
mut debug_config: ResMut<DebugRenderContext>,
keycode: Res<Input<KeyCode>>,
) {
if keycode.just_pressed(KeyCode::D) {
debug_config.enabled = !debug_config.enabled;
}
}

View File

@ -0,0 +1,34 @@
pub mod physics_replace_proxies;
pub use physics_replace_proxies::*;
pub mod utils;
pub mod controls;
pub use controls::*;
use crate::state::GameState;
use bevy::prelude::*;
use bevy_gltf_blueprints::GltfBlueprintsSet;
use bevy_rapier3d::{
prelude::{NoUserData, RapierPhysicsPlugin},
render::RapierDebugRenderPlugin,
};
pub struct PhysicsPlugin;
impl Plugin for PhysicsPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
RapierPhysicsPlugin::<NoUserData>::default(),
RapierDebugRenderPlugin::default(),
))
.register_type::<AutoAABBCollider>()
.register_type::<physics_replace_proxies::Collider>()
.add_systems(
Update,
physics_replace_proxies.after(GltfBlueprintsSet::AfterSpawn),
)
.add_systems(Update, toggle_physics_debug)
.add_systems(OnEnter(GameState::InGame), resume_physics)
.add_systems(OnExit(GameState::InGame), pause_physics);
}
}

View File

@ -0,0 +1,101 @@
use bevy::prelude::*;
// use bevy::render::primitives::Aabb;
use bevy_rapier3d::geometry::Collider as RapierCollider;
use bevy_rapier3d::prelude::{ActiveCollisionTypes, ActiveEvents, ComputedColliderShape};
use super::utils::*;
#[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>>,
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 mut rapier_collider: RapierCollider;
match collider_proxy {
Collider::Ball(radius) => {
info!("generating collider from proxy: ball");
rapier_collider = RapierCollider::ball(*radius);
commands.entity(entity)
.insert(rapier_collider)
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
;
}
Collider::Cuboid(size) => {
info!("generating collider from proxy: cuboid");
rapier_collider = RapierCollider::cuboid(size.x, size.y, size.z);
commands.entity(entity)
.insert(rapier_collider)
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
;
}
Collider::Capsule(a, b, radius) => {
info!("generating collider from proxy: capsule");
rapier_collider = RapierCollider::capsule(*a, *b, *radius);
commands.entity(entity)
.insert(rapier_collider)
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
;
}
Collider::Mesh => {
info!("generating collider from 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)
// FIXME: this is just for demo purposes !!!
.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)
}
}
}
}
}

View 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,
}
}
}

View File

@ -0,0 +1,11 @@
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;
}
}

View File

@ -0,0 +1,15 @@
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>());
}
}

View File

@ -0,0 +1,81 @@
use bevy::prelude::*;
use crate::{
assets::GameAssets,
state::{GameState, InAppRunning},
};
use bevy_gltf_blueprints::{BluePrintBundle, BlueprintName, GameWorldTag};
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>>,
) {
println!("setting up all stuff");
// 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.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<Input<KeyCode>>,
mut commands: Commands,
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) {
if keycode.just_pressed(KeyCode::T) {
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()),
transform: TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
..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);
}
}

View File

@ -0,0 +1,113 @@
use bevy::prelude::*;
use crate::state::{AppState, GameState, InMainMenu};
pub fn setup_main_menu(mut commands: Commands) {
commands.spawn((Camera2dBundle::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<Input<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::Return) {
next_app_state.set(AppState::AppLoading);
// next_game_state.set(GameState::None);
}
if keycode.just_pressed(KeyCode::L) {
next_app_state.set(AppState::AppLoading);
// load_requested_events.send(LoadRequest { path: "toto".into() })
}
if keycode.just_pressed(KeyCode::S) {
// save_requested_events.send(SaveRequest { path: "toto".into() })
}
}

View File

@ -0,0 +1,115 @@
pub mod in_game;
pub use in_game::*;
pub mod in_main_menu;
pub use in_main_menu::*;
pub mod picking;
pub use picking::*;
use crate::{
insert_dependant_component,
state::{AppState, GameState},
};
use bevy::prelude::*;
use bevy_rapier3d::prelude::*;
// this file is just for demo purposes, contains various types of components, systems etc
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub enum SoundMaterial {
Metal,
Wood,
Rock,
Cloth,
Squishy,
#[default]
None,
}
#[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 Interactible;
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/debug
pub fn test_collision_events(
mut collision_events: EventReader<CollisionEvent>,
mut contact_force_events: EventReader<ContactForceEvent>,
) {
for collision_event in collision_events.read() {
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.read() {
println!("Received contact force event: {:?}", contact_force_event);
}
}
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_plugins(PickingPlugin)
.register_type::<Interactible>()
.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>,
player_move_demo, //.run_if(in_state(AppState::Running)),
// test_collision_events
spawn_test,
)
.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

@ -0,0 +1,34 @@
use super::Player;
use bevy::prelude::*;
use bevy_gltf_blueprints::GltfBlueprintsSet;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct Pickable;
// very simple, crude picking (as in picking up objects) implementation
pub fn picking(
players: Query<&GlobalTransform, With<Player>>,
pickables: Query<(Entity, &GlobalTransform), With<Pickable>>,
mut commands: Commands,
) {
for player_transforms in players.iter() {
for (pickable, pickable_transforms) in pickables.iter() {
let distance = player_transforms
.translation()
.distance(pickable_transforms.translation());
if distance < 2.5 {
commands.entity(pickable).despawn_recursive();
}
}
}
}
pub struct PickingPlugin;
impl Plugin for PickingPlugin {
fn build(&self, app: &mut App) {
app.register_type::<Pickable>()
.add_systems(Update, (picking.after(GltfBlueprintsSet::AfterSpawn),));
}
}

View File

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

View File

@ -0,0 +1,54 @@
use bevy::app::AppExit;
use bevy::prelude::*;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, States)]
pub enum AppState {
#[default]
CoreLoading,
MenuRunning,
AppLoading,
AppRunning,
AppEnding,
// FIXME: not sure
LoadingGame,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, States)]
pub enum GameState {
#[default]
None,
InMenu,
InGame,
InGameOver,
InSaving,
InLoading,
}
// tag components for all entities within a certain state (for despawning them if needed) , FIXME: seems kinda hack-ish
#[derive(Component)]
pub struct InCoreLoading;
#[derive(Component, Default)]
pub struct InMenuRunning;
#[derive(Component)]
pub struct InAppLoading;
#[derive(Component)]
pub struct InAppRunning;
// components for tagging in game vs in game menu stuff
#[derive(Component, Default)]
pub struct InMainMenu;
#[derive(Component, Default)]
pub struct InMenu;
#[derive(Component, Default)]
pub struct InGame;
pub struct StatePlugin;
impl Plugin for StatePlugin {
fn build(&self, app: &mut App) {
app.add_state::<AppState>().add_state::<GameState>();
}
}

View File

@ -0,0 +1,80 @@
use bevy::prelude::*;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct UnitTest;
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
#[reflect(Component)]
struct TuppleTestF32(f32);
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
#[reflect(Component)]
struct TuppleTestU64(u64);
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
#[reflect(Component)]
pub struct TuppleTestStr(String);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TuppleTest2(f32, u64, String);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TuppleTestBool(bool);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TuppleVec2(Vec2);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TuppleVec3(Vec3);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TuppleVec(Vec<String>);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TuppleTestColor(Color);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct BasicTest {
a: f32,
b: u64,
c: String,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub enum EnumTest {
Metal,
Wood,
Rock,
Cloth,
Squishy,
#[default]
None,
}
pub struct ComponentsTestPlugin;
impl Plugin for ComponentsTestPlugin {
fn build(&self, app: &mut App) {
app.register_type::<BasicTest>()
.register_type::<UnitTest>()
.register_type::<TuppleTestF32>()
.register_type::<TuppleTestU64>()
.register_type::<TuppleTestStr>()
.register_type::<TuppleTestBool>()
.register_type::<TuppleTest2>()
.register_type::<TuppleVec2>()
.register_type::<TuppleVec3>()
.register_type::<EnumTest>()
.register_type::<TuppleTestColor>()
.register_type::<TuppleVec>()
.register_type::<Vec<String>>();
}
}

View File

@ -44,6 +44,9 @@ This [Blender addon](./)
- export folder: root folder to export models too
- export scene settings: exports "global"/scene settings like ambient color, bloom, ao, etc
This automatically generates additional components at the scene level
- pick your main (level) scenes and library scenes (see the chapter about Blueprints below)
- click in the scene picker & select your scene

View File

@ -1,7 +1,7 @@
bl_info = {
"name": "gltf_auto_export",
"author": "kaosigh",
"version": (0, 6, 0),
"version": (0, 7, 0),
"blender": (3, 4, 0),
"location": "File > Import-Export",
"description": "glTF/glb auto-export",

View File

@ -7,6 +7,8 @@ from .helpers_collections import (get_exportable_collections, get_collections_pe
from .helpers_export import (export_main_scene, export_blueprints_from_collections)
from .helpers import (check_if_blueprints_exist, check_if_blueprint_on_disk)
from .materials import cleanup_materials, clear_material_info, clear_materials_scene, export_materials, generate_materials_scenes, get_all_materials
from .scene_components import upsert_scene_components
from .config import scene_key
"""Main function"""
@ -46,6 +48,8 @@ def auto_export(changes_per_scene, changed_export_parameters):
print("error setting preferences from saved settings", error)
bpy.context.window_manager['__gltf_auto_export_initialized'] = True
# have the export parameters (not auto export, just gltf export) have changed: if yes (for example switch from glb to gltf, compression or not, animations or not etc), we need to re-export everything
print ("changed_export_parameters", changed_export_parameters)
try:
@ -58,12 +62,18 @@ def auto_export(changes_per_scene, changed_export_parameters):
export_output_folder = getattr(addon_prefs,"export_output_folder")
export_materials_library = getattr(addon_prefs,"export_materials_library")
export_scene_settings = getattr(addon_prefs,"export_scene_settings")
[main_scene_names, level_scenes, library_scene_names, library_scenes] = get_scenes(addon_prefs)
print("main scenes", main_scene_names, "library_scenes", library_scene_names)
print("export_output_folder", export_output_folder)
if export_scene_settings:
# inject/ update scene components
upsert_scene_components(bpy.context.scene, world = bpy.context.scene.world)
# export everything everytime
if export_blueprints:
print("EXPORTING")
@ -125,7 +135,8 @@ def auto_export(changes_per_scene, changed_export_parameters):
# first export any main/level/world scenes
print("export MAIN scenes")
for scene_name in main_scene_names:
do_export_main_scene = changed_export_parameters or (scene_name in changes_per_scene.keys() and len(changes_per_scene[scene_name].keys()) > 0) or not check_if_blueprint_on_disk(scene_name, export_levels_path, gltf_extension)
# we have more relaxed rules to determine if the main scenes have changed : any change is ok, (allows easier handling of changes, render settings etc)
do_export_main_scene = changed_export_parameters or scene_name in changes_per_scene.keys() or not check_if_blueprint_on_disk(scene_name, export_levels_path, gltf_extension)
if do_export_main_scene:
print(" exporting scene:", scene_name)
export_main_scene(bpy.data.scenes[scene_name], folder_path, addon_prefs, collections)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -54,6 +54,13 @@ class AutoExportGltfAddonPreferences(AddonPreferences):
description='The name of the library scene to auto export',
default='Library'
)
# scene components
export_scene_settings: BoolProperty(
name='Export scene settings',
description='Export scene settings ie AmbientLighting, Bloom, AO etc',
default=True
)
# blueprint settings
export_blueprints: BoolProperty(
name='Export Blueprints',

View File

@ -0,0 +1,67 @@
import bpy
from .helpers import make_empty3
# helpers to export scene level data
def upsert_scene_components(scene, world):
lighting_components = None
for object in scene.objects:
if object.name == "lighting_components":
lighting_components = object
break
if lighting_components is None:
lighting_components = make_empty3('lighting_components', [0,0,0], [0,0,0], [0,0,0], None)
if world is not None:
lighting_components['AmbientLightSettings'] = ambient_color_to_component(world)
lighting_components['ShadowmapSettings'] = scene_shadows_to_component(scene)
if scene.eevee.use_bloom:
lighting_components['BloomSettings'] = scene_bloom_to_component(scene)
elif 'BloomSettings' in lighting_components:
del lighting_components['BloomSettings']
if scene.eevee.use_gtao:
lighting_components['SSAOSettings'] = scene_ao_to_component(scene)
elif 'SSAOSettings' in lighting_components:
del lighting_components['SSAOSettings']
def ambient_color_to_component(world):
color = None
strength = None
try:
color = world.node_tree.nodes['Background'].inputs[0].default_value
strength = world.node_tree.nodes['Background'].inputs[1].default_value
except Exception as ex:
print("failed to parse ambient color: Only backgroud is supported")
if color is not None and strength is not None:
#print("color", color[0], color[1], color[2], color[3])
# print("strength", strength)
colorRgba = "Rgba(red: "+ str(color[0]) + ", green: "+ str(color[1]) + ", blue: " + str(color[2]) + ", alpha: "+ str(color[3]) + ")" # TODO: YIKES clean this up
#colorRgba = "Rgba(red: 0.0, green: 0.0, blue:0.0, alpha:0.0)"
component = "( color:"+ str(colorRgba) +", brightness:"+str(strength)+")"
print("component", component)
return component
return None
def scene_shadows_to_component(scene):
cascade_resolution = scene.eevee.shadow_cascade_size
component = "(size: "+ cascade_resolution +")"
return component
def scene_bloom_to_component(scene):
component = "BloomSettings(intensity: "+ str(scene.eevee.bloom_intensity) +")"
return component
def scene_ao_to_component(scene):
ssao = scene.eevee.use_gtao
component= "SSAOSettings()"
return component

View File

@ -203,6 +203,7 @@ class GLTF_PT_auto_export_root(bpy.types.Panel):
layout.active = operator.auto_export
layout.prop(operator, 'will_save_settings')
layout.prop(operator, "export_output_folder")
layout.prop(operator, "export_scene_settings")
# scene selectors
row = layout.row()