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::clouds::CloudPlugin;
use crate::cover::GroundCoverPlugin;
use crate::dust::DustPlugin;
use crate::fog::FogPlugin;
use crate::gpu::readback::ReadbackPlugin;
use crate::hydrology::{HydrologyPlugin, WaterPlugin};
use crate::lightning::LightningPlugin;
use crate::precipitation::PrecipitationPlugin;
use crate::query::EnvironmentSnapshot;
use crate::terrain::TerrainPlugin;
use crate::weathered::WeatheredPlugin;
use crate::wind::{WindPlugin, WindState};
pub struct ElementsPlugins;
impl PluginGroup for ElementsPlugins {
fn build(self) -> PluginGroupBuilder {
let group = PluginGroupBuilder::start::<Self>()
.add(ClimatePlugin)
.add(CelestialPlugin)
.add(WindPlugin)
.add(TerrainPlugin)
.add(HydrologyPlugin)
.add(WaterPlugin)
.add(GroundCoverPlugin)
.add(DustPlugin)
.add(FogPlugin)
.add(CloudPlugin)
.add(LightningPlugin)
.add(PrecipitationPlugin)
.add(WeatheredPlugin);
#[cfg(feature = "isomesh")]
let group = group.add(crate::voxel::VoxelPlugin);
group
}
}
#[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 terrain_max_resolution(self) -> u32 {
match self {
Self::Low => 256,
Self::Medium | Self::High => 512,
Self::Ultra => 2048,
}
}
pub fn hydrology_substeps(self) -> u32 {
match self {
Self::Low => 2,
Self::Medium | Self::High => 4,
Self::Ultra => 16,
}
}
pub fn fog_steps(self) -> u32 {
match self {
Self::Low => 32,
Self::Medium => 48,
Self::High => 64,
Self::Ultra => 96,
}
}
pub fn precipitation_particles(self) -> u32 {
match self {
Self::Low => 20_000,
Self::Medium => 60_000,
Self::High => 150_000,
Self::Ultra => 300_000,
}
}
}
#[derive(Resource, Default, Clone, Copy, Debug)]
pub(crate) struct ElementsView {
pub entity: Option<Entity>,
pub position: Vec3,
pub velocity: 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");
load_shader_library!(app, "surface_cover.wesl");
if let Some(mut buffers) = app
.world_mut()
.get_resource_mut::<Assets<bevy::render::storage::ShaderBuffer>>()
{
crate::terrain::insert_placeholder(&mut buffers);
}
load_shader_library!(app, "climate/snow.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,
VolumetricFog {
ambient_intensity: 0.0,
step_count: quality.fog_steps(),
..default()
},
DistanceFog::default(),
Exposure::SUNLIGHT,
AmbientLight {
brightness: 0.0,
..default()
},
));
commands.entity(add.entity).insert(Msaa::Off);
if atmospheres.is_empty() {
let medium = media.add(ScatteringMedium::default());
commands.spawn((Name::new("Elements atmosphere"), Atmosphere::earth(medium)));
}
}
fn follow_camera(
time: Res<Time<Virtual>>,
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)) => {
let dt = time.delta_secs();
view.velocity = if view.entity == Some(entity) && dt > 0.0 {
(position - view.position) / dt
} else {
Vec3::ZERO
};
view.entity = Some(entity);
view.position = position;
}
None => {
view.entity = None;
view.velocity = Vec3::ZERO;
}
}
}