265 lines
8.2 KiB
Rust
265 lines
8.2 KiB
Rust
//! Serializable project settings loaded from `assets/project.ron`.
|
|
|
|
mod plugin;
|
|
mod rendering;
|
|
|
|
pub use plugin::{
|
|
save_project_settings, sync_sim_tuning, ProjectSettingsChanged, ProjectSettingsIo,
|
|
ProjectSettingsPlugin, SimTuning,
|
|
};
|
|
pub use rendering::{
|
|
effective_auto_exposure, resolve_effective_render_stack, resolve_gi_path,
|
|
write_effective_render_stack, ActiveCameraRenderProfile, ActiveVolumeContribution,
|
|
EffectiveRenderStack, ExposureMode, GiMode, GiPath, RenderFallbackReason,
|
|
RenderingCapabilities, VolumeContribution,
|
|
};
|
|
|
|
use bevy::prelude::*;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
pub const DEFAULT_PROJECT_PATH: &str = "assets/project.ron";
|
|
pub const SETTINGS_VERSION: u32 = 1;
|
|
|
|
/// Marker for cameras that receive the shared project rendering stack.
|
|
#[derive(Component, Debug, Clone, Copy, Default)]
|
|
pub struct ProjectRenderCamera;
|
|
|
|
/// Root project manifest and tunable sections.
|
|
#[derive(Resource, Debug, Clone, Serialize, Deserialize, Reflect)]
|
|
#[reflect(Resource, Clone)]
|
|
pub struct ProjectSettings {
|
|
pub version: u32,
|
|
pub name: String,
|
|
pub default_level: String,
|
|
pub asset_roots: Vec<String>,
|
|
pub rendering: RenderingSettings,
|
|
pub physics: PhysicsSettings,
|
|
pub input: InputSettings,
|
|
}
|
|
|
|
impl Default for ProjectSettings {
|
|
fn default() -> Self {
|
|
Self {
|
|
version: SETTINGS_VERSION,
|
|
name: "Bevy FPS Foundation".into(),
|
|
default_level: "assets/levels/editor_scene.scn.ron".into(),
|
|
asset_roots: vec![
|
|
"assets/models".into(),
|
|
"assets/textures".into(),
|
|
"assets/materials".into(),
|
|
"assets/levels".into(),
|
|
],
|
|
rendering: RenderingSettings::default(),
|
|
physics: PhysicsSettings::default(),
|
|
input: InputSettings::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Camera post-processing and world lighting defaults.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Reflect)]
|
|
#[reflect(Clone)]
|
|
pub struct RenderingSettings {
|
|
#[serde(default)]
|
|
pub gi_mode: GiMode,
|
|
pub hdr: bool,
|
|
#[serde(default)]
|
|
pub exposure_mode: ExposureMode,
|
|
pub exposure_ev100: f32,
|
|
#[serde(default = "default_auto_exposure_speed_brighten")]
|
|
pub auto_exposure_speed_brighten: f32,
|
|
#[serde(default = "default_auto_exposure_speed_darken")]
|
|
pub auto_exposure_speed_darken: f32,
|
|
pub tonemapping_aces: bool,
|
|
pub bloom: bool,
|
|
pub taa: bool,
|
|
pub ssao: bool,
|
|
pub atmosphere: bool,
|
|
pub fog_color: [f32; 3],
|
|
pub fog_density: f32,
|
|
pub ambient_color: [f32; 3],
|
|
pub ambient_brightness: f32,
|
|
pub sun_illuminance: f32,
|
|
pub shadow_cascades: u32,
|
|
pub shadow_first_cascade: f32,
|
|
pub shadow_max_distance: f32,
|
|
}
|
|
|
|
fn default_auto_exposure_speed_brighten() -> f32 {
|
|
3.0
|
|
}
|
|
|
|
fn default_auto_exposure_speed_darken() -> f32 {
|
|
1.0
|
|
}
|
|
|
|
impl Default for RenderingSettings {
|
|
fn default() -> Self {
|
|
Self {
|
|
gi_mode: GiMode::default(),
|
|
hdr: true,
|
|
exposure_mode: ExposureMode::default(),
|
|
exposure_ev100: 13.0,
|
|
auto_exposure_speed_brighten: default_auto_exposure_speed_brighten(),
|
|
auto_exposure_speed_darken: default_auto_exposure_speed_darken(),
|
|
tonemapping_aces: true,
|
|
bloom: true,
|
|
taa: true,
|
|
ssao: true,
|
|
atmosphere: true,
|
|
fog_color: [0.66, 0.75, 0.86],
|
|
fog_density: 0.0012,
|
|
ambient_color: [0.6, 0.7, 0.85],
|
|
ambient_brightness: 60.0,
|
|
sun_illuminance: 100_000.0,
|
|
shadow_cascades: 4,
|
|
shadow_first_cascade: 6.0,
|
|
shadow_max_distance: 220.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Gameplay movement and capsule constants (mirrors former `sim::tuning`).
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Reflect)]
|
|
#[reflect(Clone)]
|
|
pub struct PhysicsSettings {
|
|
pub player_radius: f32,
|
|
pub player_length: f32,
|
|
pub eye_offset_stand: f32,
|
|
pub eye_offset_crouch: f32,
|
|
pub gravity: f32,
|
|
pub walk_speed: f32,
|
|
pub run_speed: f32,
|
|
pub crouch_speed: f32,
|
|
pub jump_speed: f32,
|
|
pub ground_accel: f32,
|
|
pub air_accel: f32,
|
|
pub ground_check_dist: f32,
|
|
pub skin: f32,
|
|
pub max_slide_iter: u32,
|
|
pub ground_normal_y: f32,
|
|
pub coyote_time: f32,
|
|
pub jump_buffer: f32,
|
|
}
|
|
|
|
impl Default for PhysicsSettings {
|
|
fn default() -> Self {
|
|
Self {
|
|
player_radius: 0.4,
|
|
player_length: 1.0,
|
|
eye_offset_stand: 0.6,
|
|
eye_offset_crouch: 0.1,
|
|
gravity: 22.0,
|
|
walk_speed: 6.0,
|
|
run_speed: 9.5,
|
|
crouch_speed: 3.0,
|
|
jump_speed: 7.5,
|
|
ground_accel: 70.0,
|
|
air_accel: 16.0,
|
|
ground_check_dist: 0.2,
|
|
skin: 0.02,
|
|
max_slide_iter: 4,
|
|
ground_normal_y: 0.5,
|
|
coyote_time: 0.12,
|
|
jump_buffer: 0.12,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Input sensitivities and editor fly camera speeds.
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Reflect)]
|
|
#[reflect(Clone)]
|
|
pub struct InputSettings {
|
|
pub yaw_sensitivity: f32,
|
|
pub pitch_sensitivity: f32,
|
|
pub editor_fly_speed: f32,
|
|
pub editor_look_sensitivity: f32,
|
|
pub editor_pan_speed: f32,
|
|
}
|
|
|
|
impl Default for InputSettings {
|
|
fn default() -> Self {
|
|
Self {
|
|
yaw_sensitivity: 0.003,
|
|
pitch_sensitivity: 0.0022,
|
|
editor_fly_speed: 14.0,
|
|
editor_look_sensitivity: 0.003,
|
|
editor_pan_speed: 0.015,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Loads settings from disk, falling back to defaults.
|
|
pub fn load_project_settings_from_path(path: &str) -> ProjectSettings {
|
|
match std::fs::read_to_string(path) {
|
|
Ok(contents) => match ron::from_str(&contents) {
|
|
Ok(settings) => settings,
|
|
Err(error) => {
|
|
warn!("Failed to parse {path}: {error}; using defaults");
|
|
ProjectSettings::default()
|
|
}
|
|
},
|
|
Err(error) => {
|
|
warn!("Could not read {path}: {error}; using defaults");
|
|
ProjectSettings::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Serializes settings to RON.
|
|
pub fn save_project_settings_to_string(settings: &ProjectSettings) -> Result<String, ron::Error> {
|
|
ron::ser::to_string_pretty(settings, ron::ser::PrettyConfig::default())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn settings_round_trip() {
|
|
let settings = ProjectSettings::default();
|
|
let text = save_project_settings_to_string(&settings).expect("serialize");
|
|
let parsed: ProjectSettings = ron::from_str(&text).expect("deserialize");
|
|
assert_eq!(parsed.name, settings.name);
|
|
assert_eq!(parsed.physics.walk_speed, settings.physics.walk_speed);
|
|
assert_eq!(parsed.rendering.gi_mode, settings.rendering.gi_mode);
|
|
}
|
|
|
|
#[test]
|
|
fn gi_mode_defaults_auto() {
|
|
assert_eq!(RenderingSettings::default().gi_mode, GiMode::Auto);
|
|
}
|
|
|
|
#[test]
|
|
fn exposure_mode_auto_round_trip() {
|
|
let mut settings = ProjectSettings::default();
|
|
settings.rendering.exposure_mode = ExposureMode::Auto;
|
|
settings.rendering.auto_exposure_speed_brighten = 4.0;
|
|
settings.rendering.auto_exposure_speed_darken = 0.5;
|
|
let text = save_project_settings_to_string(&settings).expect("serialize");
|
|
let parsed: ProjectSettings = ron::from_str(&text).expect("deserialize");
|
|
assert_eq!(parsed.rendering.exposure_mode, ExposureMode::Auto);
|
|
assert_eq!(parsed.rendering.auto_exposure_speed_brighten, 4.0);
|
|
assert_eq!(parsed.rendering.auto_exposure_speed_darken, 0.5);
|
|
}
|
|
|
|
#[test]
|
|
fn effective_auto_exposure_requires_hdr_and_compute() {
|
|
let mut profile = ActiveCameraRenderProfile::default();
|
|
profile.exposure_mode = ExposureMode::Auto;
|
|
profile.hdr = true;
|
|
let caps = RenderingCapabilities {
|
|
auto_exposure_supported: true,
|
|
..Default::default()
|
|
};
|
|
assert!(effective_auto_exposure(&profile, &caps));
|
|
|
|
profile.hdr = false;
|
|
assert!(!effective_auto_exposure(&profile, &caps));
|
|
|
|
profile.hdr = true;
|
|
let caps = RenderingCapabilities::default();
|
|
assert!(!effective_auto_exposure(&profile, &caps));
|
|
}
|
|
}
|