use bevy::app::PluginGroupBuilder;
use bevy::camera::Exposure;
use bevy::camera::Hdr;
use bevy::core_pipeline::prepass::DepthPrepass;
use bevy::light::atmosphere::ScatteringMedium;
use bevy::light::{Atmosphere, AtmosphereEnvironmentMapLight, VolumetricFog};
use bevy::pbr::{AtmosphereSettings, DistanceFog};
use bevy::prelude::*;
use bevy::render::view::Msaa;
use bevy::shader::load_shader_library;
use crate::celestial::{CelestialPlugin, SunState, clock_plugin};
use crate::climate::{ClimatePlugin, Weather};
use crate::fog::FogPlugin;
use crate::gpu::readback::ReadbackPlugin;
use crate::query::EnvironmentSnapshot;
use crate::wind::{WindPlugin, WindState};
pub struct ElementsPlugins;
impl PluginGroup for ElementsPlugins {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
.add(ClimatePlugin)
.add(CelestialPlugin)
.add(WindPlugin)
.add(FogPlugin)
}
}
#[derive(SystemSet, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ElementsSystems {
Clock,
Weather,
Celestial,
Simulation,
Readback,
}
#[derive(Component, Clone, Copy, Debug, Reflect)]
#[require(Hdr)]
pub struct ElementsCamera {
pub auto_exposure: bool,
}
impl Default for ElementsCamera {
fn default() -> Self {
Self {
auto_exposure: true,
}
}
}
#[derive(Resource, Default, Clone, Copy, Debug, PartialEq, Eq, Hash, Reflect)]
pub enum ElementsQuality {
Low,
Medium,
#[default]
High,
Ultra,
}
impl ElementsQuality {
pub fn wind_grid(self) -> UVec3 {
match self {
Self::Low => UVec3::new(32, 8, 32),
Self::Medium => UVec3::new(48, 12, 48),
Self::High => UVec3::new(64, 16, 64),
Self::Ultra => UVec3::new(96, 24, 96),
}
}
pub fn wind_pressure_iterations(self) -> u32 {
match self {
Self::Low | Self::Medium => 10,
Self::High => 12,
Self::Ultra => 40,
}
}
pub fn fog_steps(self) -> u32 {
match self {
Self::Low => 32,
Self::Medium => 48,
Self::High => 64,
Self::Ultra => 96,
}
}
}
#[derive(Resource, Default, Clone, Copy, Debug)]
pub(crate) struct ElementsView {
pub entity: Option<Entity>,
pub position: Vec3,
}
pub(crate) struct ElementsCorePlugin;
impl Plugin for ElementsCorePlugin {
fn build(&self, app: &mut App) {
clock_plugin(app);
app.configure_sets(
Update,
(
ElementsSystems::Clock,
ElementsSystems::Weather,
ElementsSystems::Celestial,
ElementsSystems::Simulation,
ElementsSystems::Readback,
)
.chain(),
)
.init_resource::<ElementsQuality>()
.init_resource::<ElementsView>()
.init_resource::<EnvironmentSnapshot>()
.init_resource::<Weather>()
.init_resource::<SunState>()
.init_resource::<WindState>()
.add_systems(Update, follow_camera.in_set(ElementsSystems::Clock));
}
}
pub(crate) struct ElementsGpuCorePlugin;
impl Plugin for ElementsGpuCorePlugin {
fn build(&self, app: &mut App) {
ensure_core(app);
load_shader_library!(app, "noise.wesl");
load_shader_library!(app, "grid.wesl");
load_shader_library!(app, "wind.wesl");
app.add_plugins(ReadbackPlugin).add_observer(dress_camera);
}
}
pub(crate) fn ensure_core(app: &mut App) {
if !app.is_plugin_added::<ElementsCorePlugin>() {
app.add_plugins(ElementsCorePlugin);
}
}
pub(crate) fn ensure_gpu_core(app: &mut App) {
if !app.is_plugin_added::<ElementsGpuCorePlugin>() {
app.add_plugins(ElementsGpuCorePlugin);
}
}
fn dress_camera(
add: On<Add<ElementsCamera>>,
mut commands: Commands,
quality: Res<ElementsQuality>,
atmospheres: Query<(), With<Atmosphere>>,
mut media: ResMut<Assets<ScatteringMedium>>,
) {
commands.entity(add.entity).insert_if_new((
AtmosphereSettings::default(),
AtmosphereEnvironmentMapLight::default(),
DepthPrepass,
Msaa::Off,
VolumetricFog {
ambient_intensity: 0.0,
step_count: quality.fog_steps(),
..default()
},
DistanceFog::default(),
Exposure::SUNLIGHT,
AmbientLight {
brightness: 0.0,
..default()
},
));
if atmospheres.is_empty() {
let medium = media.add(ScatteringMedium::default());
commands.spawn((Name::new("Elements atmosphere"), Atmosphere::earth(medium)));
}
}
fn follow_camera(
cameras: Query<(Entity, &Camera, &GlobalTransform), With<ElementsCamera>>,
mut view: ResMut<ElementsView>,
mut warned: Local<bool>,
) {
let mut count = 0usize;
let mut best: Option<(Entity, isize, Vec3)> = None;
for (entity, camera, transform) in &cameras {
count += 1;
if best.is_none_or(|(_, order, _)| camera.order < order) {
best = Some((entity, camera.order, transform.translation()));
}
}
if count > 1 && !*warned {
warn!(
"{count} entities carry ElementsCamera; the environment follows the one with the lowest Camera::order"
);
*warned = true;
}
match best {
Some((entity, _, position)) => {
view.entity = Some(entity);
view.position = position;
}
None => view.entity = None,
}
}