mod common;
use std::time::Duration;
use bevy::asset::RenderAssetUsages;
use bevy::prelude::*;
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat};
use bevy_elements::*;
use common::{ExampleHarness, camera};
const CELLS: u32 = 256;
const CELL_M: f32 = 1.0;
const RANGE_M: f32 = 12.0;
const PLAIN_M: f32 = 6.0;
const BASIN_RADIUS_M: f32 = 25.0;
const BASIN_DEPTH_M: f32 = 4.0;
fn main() {
App::new()
.add_plugins((DefaultPlugins, ElementsPlugins, ExampleHarness))
.insert_resource(WorldClock {
paused: true,
..WorldClock::new(CivilDate::new(2026, 6, 21)).at_hour(15.0)
})
.insert_resource(Climate::temperate())
.insert_resource(ElementsQuality::Ultra)
.insert_resource(HydrologySettings {
time_scale: 16.0,
boundary: HydrologyBoundary::Closed,
evaporation: false,
..default()
})
.insert_resource(ErosionSettings {
enabled: false,
..default()
})
.add_systems(Startup, setup)
.add_systems(
Update,
(speed_keys, close_the_tap).after(ElementsSystems::Readback),
)
.run();
}
fn ground_m(x: f32, z: f32) -> f32 {
let r = (x * x + z * z).sqrt() / BASIN_RADIUS_M;
let bowl = if r < 1.0 {
0.5 * (1.0 + (std::f32::consts::PI * r).cos())
} else {
0.0
};
PLAIN_M - BASIN_DEPTH_M * bowl
}
fn basin_map() -> Image {
let mut data = Vec::with_capacity((CELLS * CELLS) as usize * 4);
for j in 0..CELLS {
for i in 0..CELLS {
let x = (i as f32 + 0.5 - CELLS as f32 * 0.5) * CELL_M;
let z = (j as f32 + 0.5 - CELLS as f32 * 0.5) * CELL_M;
data.extend_from_slice(&(ground_m(x, z) / RANGE_M).to_le_bytes());
}
}
Image::new(
Extent3d {
width: CELLS,
height: CELLS,
depth_or_array_layers: 1,
},
TextureDimension::D2,
data,
TextureFormat::R32Float,
RenderAssetUsages::default(),
)
}
fn setup(
mut commands: Commands,
mut images: ResMut<Assets<Image>>,
mut director: ResMut<WeatherDirector>,
) {
let map = images.add(basin_map());
commands.spawn(
Terrain::from_heightmap(map, 0.0..RANGE_M)
.with_extent(Vec2::splat(CELLS as f32 * CELL_M))
.with_resolution(UVec2::splat(CELLS)),
);
commands.spawn((
WaterSource {
rate_m3_s: 60.0,
radius_m: 3.0,
},
Transform::from_xyz(0.0, ground_m(0.0, 0.0), 0.0),
));
commands.spawn(camera(
Vec3::new(-48.0, 17.0, 38.0),
Vec3::new(0.0, 4.0, -4.0),
));
director.force(WeatherPreset::Clear, Duration::ZERO);
}
const FULL_DEPTH_M: f32 = 3.7;
fn close_the_tap(env: Environment, mut sources: Query<&mut WaterSource>) {
if env
.water_depth(Vec2::ZERO)
.is_some_and(|depth| depth >= FULL_DEPTH_M)
{
for mut source in &mut sources {
if source.rate_m3_s != 0.0 {
source.rate_m3_s = 0.0;
}
}
}
}
fn speed_keys(keys: Res<ButtonInput<KeyCode>>, mut settings: ResMut<HydrologySettings>) {
if keys.just_pressed(KeyCode::KeyG) {
settings.time_scale *= 2.0;
}
if keys.just_pressed(KeyCode::KeyH) {
settings.time_scale *= 0.5;
}
}