From 0f17a2523a1c070ac56fdbdbfccbc0fbefbf49b8 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Mon, 27 Nov 2023 19:53:46 -0800 Subject: [PATCH] Integrating fog and adding color grading to tweakfile --- Cargo.lock | 5 + Cargo.toml | 2 +- assets/martian.tweak.toml | 24 ++- src/display3d.rs | 321 +++++++++++++++++++++++++++----------- src/tweak.rs | 10 +- 5 files changed, 254 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e08736e..91fc745 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -396,6 +396,7 @@ dependencies = [ "bevy_tasks", "bevy_utils", "bytemuck", + "serde", ] [[package]] @@ -593,6 +594,7 @@ dependencies = [ "bevy_math", "bevy_reflect", "bevy_utils", + "serde", "thiserror", ] @@ -927,6 +929,7 @@ dependencies = [ "bevy_reflect", "bevy_utils", "crossbeam-channel", + "serde", "thiserror", ] @@ -941,6 +944,7 @@ dependencies = [ "bevy_hierarchy", "bevy_math", "bevy_reflect", + "serde", ] [[package]] @@ -1014,6 +1018,7 @@ dependencies = [ "bevy_reflect", "bevy_utils", "raw-window-handle", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4a33e31..000069e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ build = "build.rs" [dependencies] bevy_fmod = { version = "0.3", features = ["live-update"] } -bevy = { version = "0.11", features = ["jpeg", "hdr"] } +bevy = { version = "0.11", features = ["jpeg", "hdr", "serialize"] } serde = "1" toml = { version = "0.8", features = ["parse"] } diff --git a/assets/martian.tweak.toml b/assets/martian.tweak.toml index 4e92761..c64e0d3 100644 --- a/assets/martian.tweak.toml +++ b/assets/martian.tweak.toml @@ -9,18 +9,28 @@ idle = "/SFX/3D/3DPickup-Idle-PutdownWhirr" invalid = "/sfx/3D/3DInvalidMove" [audio.music] -main = "/Music/Main Track2" +main = "/Music/Main Track2" [audio.menu] select = "/SFX/MenuSelect" [display3d.fog] -exponent = 2.0 +exponent = 1.0 # https://docs.rs/bevy/latest/bevy/pbr/enum.FogFalloff.html -falloff = "Atmospheric (extinction 1.0 1.0 1.0) (inscattering 1.0 1.0 1.0)" - +falloff = "Exponential 0.1" +# Examples: +# "Linear start end" +# "Exponential density" +# "ExponentialSquared density" +# "Exponential (extinction 1.0 1.0 1.0) (inscattering 1.0 1.0 1.0)" [display3d.fog.color] -Rgba = { red = 1.0, green = 1.0, blue = 1.0, alpha = 1.0 } - +Rgba = { red = 1.0, green = 0.2, blue = 0.1, alpha = 0.1 } [display3d.fog.light_color] -Rgba = { red = 1.0, green = 1.0, blue = 1.0, alpha = 1.0 } \ No newline at end of file +Rgba = { red = 1.0, green = 1.0, blue = 1.0, alpha = 0.1 } + +# https://docs.rs/bevy/latest/bevy/render/view/struct.ColorGrading.html +[display3d.color.grading] +exposure = 0.0 +gamma = 1.0 +pre_saturation = 1.0 +post_saturation = 1.0 diff --git a/src/display3d.rs b/src/display3d.rs index e5e1d56..2ab079f 100644 --- a/src/display3d.rs +++ b/src/display3d.rs @@ -1,6 +1,7 @@ use crate::{ game::{Board, BoardIndex, Piece, Side}, prelude::*, + tweak::Tweakfile, }; use bevy::{ core_pipeline::{tonemapping::DebandDither, Skybox}, @@ -11,7 +12,7 @@ use bevy::{ }, window::PrimaryWindow, }; -use serde::{Deserialize, Deserializer, de}; +use serde::{de, Deserialize, Deserializer}; pub(crate) struct Display3dPlugin; @@ -44,6 +45,7 @@ impl Plugin for Display3dPlugin { create_valid_move_entity.run_if(any_component_added::), remove_valid_move_entity.run_if(any_component_removed::()), set_valid_move_model.run_if(any_component_added::), + update_tweaks.run_if(on_event::>()), ), ) .add_systems( @@ -83,92 +85,6 @@ impl Plugin for Display3dPlugin { } } -#[derive(Debug, Deserialize, Default)] -pub(crate) struct Display3dTweaks { - #[serde(default)] - fog: FogTweaks, -} - -#[derive(Debug, Deserialize)] -struct FogTweaks { - #[serde(default)] - color: Color, - #[serde(default)] - light_color: Color, - #[serde(default)] - exponent: f32, - #[serde(default = "default_fog_falloff", deserialize_with = "deserialize_fog_falloff")] - falloff: FogFalloff, -} - -impl Default for FogTweaks { - fn default() -> Self { - FogTweaks { - color: Color::WHITE, - light_color: Color::WHITE, - exponent: 1.0, - falloff: FogFalloff::Exponential { - density: 1.0, - } - } - } -} - -fn default_fog_falloff() -> FogFalloff { - FogFalloff::Exponential { - density: 1.0, - } -} - -fn deserialize_fog_falloff<'de, D: Deserializer<'de>>(deserializer: D) -> Result { - if let Ok(s) = String::deserialize(deserializer) { - // Linear [start f32] [end f32] - if s.starts_with("Linear") { - let mut parts = s.split(' '); - let start: f32 = parts.nth(1).expect("Fog Linear Start").parse().expect("Floating point number"); - let end: f32 = parts.nth(2).expect("Fog Linear End").parse().expect("Floating point number"); - Ok(FogFalloff::Linear { start, end }) - // Exponential [density f32] - } else if s.starts_with("Exponential") { - let mut parts = s.split(' '); - let density: f32 = parts.nth(1).expect("Fog Exponential Density").parse().expect("Floating point number"); - Ok(FogFalloff::Exponential { density }) - // ExponentialSquared [density f32] - } else if s.starts_with("ExponentialSquared") { - let mut parts = s.split(' '); - let density: f32 = parts.nth(1).expect("Fog Exponential Density Squared").parse().expect("Floating point number"); - Ok(FogFalloff::ExponentialSquared { density }) - // Atmospheric (extinction [r: f32] [g: f32] [b: f32]) (inscattering [r: f32] [g: f32] [b: f32]) - } else if s.starts_with("Atmospheric") { - let extinction = { - let start = s.find("extinction").expect("Extinction start"); - let end = s[start..].match_indices(')').find_map(|(i, _)| Some(i)).expect("Extinction end"); - let mut parts = s[start..start+end].split(' '); - let _ = parts.next(); - let r: f32 = parts.next().expect("red value").parse().expect("Floating point number"); - let g: f32 = parts.next().expect("green value").parse().expect("Floating point number"); - let b: f32 = parts.next().expect("blue value").parse().expect("Floating point number"); - Vec3::new(r, g, b) - }; - let inscattering = { - let start = s.find("inscattering").expect("Inscattering start"); - let end = s[start..].match_indices(')').find_map(|(i, _)| Some(i)).expect("Inscattering end"); - let mut parts = s[start..start+end].split(' '); - let _ = parts.next(); - let r: f32 = parts.next().expect("red value").parse().expect("Floating point number"); - let g: f32 = parts.next().expect("green value").parse().expect("Floating point number"); - let b: f32 = parts.next().expect("blue value").parse().expect("Floating point number"); - Vec3::new(r, g, b) - }; - Ok(FogFalloff::Atmospheric { extinction, inscattering }) - } else { - Err(de::Error::custom("Failed to parse fog value")) - } - } else { - Err(de::Error::custom("Failed to parse fog value")) - } -} - #[derive(Debug, Component)] pub(crate) struct Display3d; @@ -318,6 +234,26 @@ fn hydrate_camera( }); } +fn update_tweaks( + mut events: EventReader>, + mut camera_settings: Query<(&mut FogSettings, &mut ColorGrading), With>, + tweaks: Res>, +) { + events.iter().for_each(|event| match event { + AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { + if let Some(tweak) = tweaks.get(handle) { + camera_settings + .iter_mut() + .for_each(|(mut fog, mut color_grading)| { + *fog = tweak.display3d.fog.clone().into(); + *color_grading = tweak.display3d.color.grading.clone().into(); + }); + } + } + AssetEvent::Removed { .. } => debug!("Tweakfile removal not handled"), + }); +} + fn fix_skybox(mut images: ResMut>, assets: Res) { let image = images.get_mut(&assets.skybox).unwrap(); info!("Loaded skybox image"); @@ -353,8 +289,22 @@ fn set_piece_model( } fn set_board_model( - mut boards: Query<&mut Handle, (With, Without, With)>, - mut tiles: Query<&mut Handle, (With, Without, With)>, + mut boards: Query< + &mut Handle, + ( + With, + Without, + With, + ), + >, + mut tiles: Query< + &mut Handle, + ( + With, + Without, + With, + ), + >, assets_map: Res, gltfs: Res>, ) { @@ -668,7 +618,9 @@ fn create_valid_move_entity( .map(|i| Transform::from_translation(board_translation(i))) .for_each(|t| { commands.entity(board_entity).with_children(|parent| { - parent.spawn((Display3d, game::ValidMove, SceneBundle { ..default() })).insert(t); + parent + .spawn((Display3d, game::ValidMove, SceneBundle { ..default() })) + .insert(t); }); }); } @@ -681,13 +633,13 @@ fn set_valid_move_model( assets_map: Res, ) { if let Some(gltf) = gltfs.get(&assets_map.models) { - events.iter_mut().for_each(|mut handle| *handle = gltf.named_scenes.get("Valid Move Spot").unwrap().clone()) + events.iter_mut().for_each(|mut handle| { + *handle = gltf.named_scenes.get("Valid Move Spot").unwrap().clone() + }) } } -fn play_valid_move_animation( - players: Query<&AnimationPlayer>, -) { +fn play_valid_move_animation(players: Query<&AnimationPlayer>) { todo!(); } @@ -797,3 +749,184 @@ fn switch_sides( ); }); } + +pub(crate) mod tweaks { + use super::*; + + #[derive(Debug, Deserialize, Default)] + pub(crate) struct Display3dTweaks { + #[serde(default)] + pub fog: FogTweaks, + #[serde(default)] + pub color: ColorTweaks, + } + + #[derive(Debug, Deserialize, Default)] + pub(super) struct ColorTweaks { + #[serde(default)] + pub grading: ColorGradingTweaks, + } + + #[derive(Debug, Deserialize, Default, Clone)] + pub(super) struct ColorGradingTweaks { + pub exposure: f32, + pub gamma: f32, + pub pre_saturation: f32, + pub post_saturation: f32, + } + + impl Into for ColorGradingTweaks { + fn into(self) -> ColorGrading { + ColorGrading { + exposure: self.exposure, + gamma: self.gamma, + pre_saturation: self.pre_saturation, + post_saturation: self.post_saturation, + } + } + } + + #[derive(Debug, Clone, Deserialize)] + pub(super) struct FogTweaks { + #[serde(default)] + color: Color, + #[serde(default)] + light_color: Color, + #[serde(default)] + exponent: f32, + #[serde( + default = "default_fog_falloff", + deserialize_with = "deserialize_fog_falloff" + )] + falloff: FogFalloff, + } + + impl Default for FogTweaks { + fn default() -> Self { + FogTweaks { + color: Color::WHITE, + light_color: Color::WHITE, + exponent: 1.0, + falloff: FogFalloff::Exponential { density: 1.0 }, + } + } + } + + impl Into for FogTweaks { + fn into(self) -> FogSettings { + FogSettings { + color: self.color, + directional_light_color: self.light_color, + directional_light_exponent: self.exponent, + falloff: self.falloff.clone(), + } + } + } + + fn default_fog_falloff() -> FogFalloff { + FogFalloff::Exponential { density: 1.0 } + } + + fn deserialize_fog_falloff<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result { + if let Ok(s) = String::deserialize(deserializer) { + // Linear [start f32] [end f32] + if s.starts_with("Linear") { + let mut parts = s.split(' '); + let _ = parts.next(); + let start: f32 = parts + .next() + .expect("Fog Linear Start") + .parse() + .expect("Floating point number"); + let end: f32 = parts + .next() + .expect("Fog Linear End") + .parse() + .expect("Floating point number"); + Ok(FogFalloff::Linear { start, end }) + // Exponential [density f32] + } else if s.starts_with("Exponential") { + let mut parts = s.split(' '); + let _ = parts.next(); + let density: f32 = parts + .next() + .expect("Fog Exponential Density") + .parse() + .expect("Floating point number"); + Ok(FogFalloff::Exponential { density }) + // ExponentialSquared [density f32] + } else if s.starts_with("ExponentialSquared") { + let mut parts = s.split(' '); + let _ = parts.next(); + let density: f32 = parts + .next() + .expect("Fog Exponential Density Squared") + .parse() + .expect("Floating point number"); + Ok(FogFalloff::ExponentialSquared { density }) + // Atmospheric (extinction [r: f32] [g: f32] [b: f32]) (inscattering [r: f32] [g: f32] [b: f32]) + } else if s.starts_with("Atmospheric") { + let extinction = { + let start = s.find("extinction").expect("Extinction start"); + let end = s[start..] + .match_indices(')') + .find_map(|(i, _)| Some(i)) + .expect("Extinction end"); + let mut parts = s[start..start + end].split(' '); + let _ = parts.next(); + let r: f32 = parts + .next() + .expect("red value") + .parse() + .expect("Floating point number"); + let g: f32 = parts + .next() + .expect("green value") + .parse() + .expect("Floating point number"); + let b: f32 = parts + .next() + .expect("blue value") + .parse() + .expect("Floating point number"); + Vec3::new(r, g, b) + }; + let inscattering = { + let start = s.find("inscattering").expect("Inscattering start"); + let end = s[start..] + .match_indices(')') + .find_map(|(i, _)| Some(i)) + .expect("Inscattering end"); + let mut parts = s[start..start + end].split(' '); + let _ = parts.next(); + let r: f32 = parts + .next() + .expect("red value") + .parse() + .expect("Floating point number"); + let g: f32 = parts + .next() + .expect("green value") + .parse() + .expect("Floating point number"); + let b: f32 = parts + .next() + .expect("blue value") + .parse() + .expect("Floating point number"); + Vec3::new(r, g, b) + }; + Ok(FogFalloff::Atmospheric { + extinction, + inscattering, + }) + } else { + Err(de::Error::custom("Failed to parse fog value")) + } + } else { + Err(de::Error::custom("Failed to parse fog value")) + } + } +} diff --git a/src/tweak.rs b/src/tweak.rs index 8599c39..79e00c0 100644 --- a/src/tweak.rs +++ b/src/tweak.rs @@ -12,12 +12,11 @@ pub(crate) struct TweakPlugin; impl Plugin for TweakPlugin { fn build(&self, app: &mut App) { - app.add_systems(OnEnter(GameState::Loading), load_tweakfile); app.add_asset::() .init_asset_loader::(); - } + } } fn load_tweakfile(server: Res, mut commands: Commands) { @@ -33,12 +32,11 @@ struct GameTweakfile(Handle); #[uuid = "e5768efe-edce-4267-bdf4-dd8f8ca613c7"] pub(crate) struct Tweakfile { #[serde(default)] - pub audio: audio::AudioTweaks, + pub audio: audio::AudioTweaks, #[serde(default)] - pub display3d: display3d::Display3dTweaks, + pub display3d: display3d::tweaks::Display3dTweaks, } - #[derive(Default)] pub struct TweakfileLoader; @@ -59,4 +57,4 @@ impl AssetLoader for TweakfileLoader { fn extensions(&self) -> &[&str] { &["tweak.toml"] } -} \ No newline at end of file +}