use std::time::Duration; use bevy::{asset::RenderAssetUsages, picking::PickSet, prelude::*, render::mesh::{Indices, PrimitiveTopology}}; use noise::{Fbm, MultiFractal, NoiseFn, Perlin}; use rand::{thread_rng, Rng, SeedableRng}; use voronoice::{BoundingBox, Point, VoronoiBuilder}; mod cells; mod picking; use picking::*; use cells::*; pub use cells::CellKind; use crate::{time::GameTime, ui::CurrentAction}; pub struct Plugin; impl bevy::prelude::Plugin for Plugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup) .add_systems(PreUpdate, picking_backend.in_set(PickSet::Backend)) .add_systems(Update, (update_map_mesh, cells_regeneration, expand)) .insert_resource(ClearColor(Color::srgb(0., 0., 1.))) .insert_resource(Seed(thread_rng().gen())); } } pub const HEIGHT: f32 = 2.; pub const WIDTH: f32 = 2.; pub const REAL_HEIGHT: f32 = 500.; pub const REAL_WIDTH: f32 = 500.; pub const CELL_AREA: f32 = REAL_HEIGHT * REAL_WIDTH / SIZE as f32; pub const SIZE: usize = 10000; #[derive(Resource)] struct Seed(u32); #[derive(Component)] pub struct Voronoi (voronoice::Voronoi); #[derive(Component)] pub struct MapMarker; #[derive(Component)] struct MapColors (Vec<[f32; 4]>); #[derive(Component)] pub struct CellsEntities (Vec); #[derive(Component)] pub struct MeshNeedsUpdate(bool); fn setup( mut cmds: Commands, mut meshes: ResMut>, mut materials: ResMut>, seed: Res ) { let mut rng = rand::rngs::SmallRng::seed_from_u64(seed.0 as u64); let mut sites = Vec::with_capacity(SIZE); for _ in 0..SIZE { sites.push(Point { x:rng.gen_range(-WIDTH/2.0..WIDTH/2.0) as f64, y:rng.gen_range(-HEIGHT/2.0..HEIGHT/2.0) as f64 }) } let voronoi = VoronoiBuilder::default() .set_sites(sites) .set_bounding_box(BoundingBox::new_centered(WIDTH as f64, HEIGHT as f64)) .set_lloyd_relaxation_iterations(3) .build() .unwrap(); let mut cells = Vec::with_capacity(SIZE); let z_noise = Fbm::::new(seed.0); let moisture_noise = Fbm::::new(seed.0+1) .set_frequency(2.); for i in 0..SIZE { let c = voronoi.cell(i); let site = c.site_position(); let z = ( 0.3 // Arbitrary value + ((z_noise.get([site.x, site.y])+1.)/2.) // Noise + [0; 1] - ((site.x.powi(2)+site.y.powi(2)).sqrt()*0.5) // Distance - [0; sqrt(2)] * 0.5 ).clamp(0., 1.); let m = ( (moisture_noise.get([site.x, site.y])+1.)/2. // Noise + [0; 1] ).clamp(0., 1.) as f32; let k = if z <= 0.5 { CellKind::Sea } else if z <= 0.52 { CellKind::Beach } else if z < 0.8 { CellKind::Dirt } else { CellKind::Stone }; cells.push(Cell { kind: k, voronoi_id: i, altitude: (z*255.) as u8, vertices: vec![] }); } let mut poss = Vec::new(); let mut indices = Vec::new(); for (c, cd) in voronoi.iter_cells().zip(cells.iter_mut()).filter(|(_,cd)| cd.kind != CellKind::Forest) { let vs = c.iter_vertices().collect::>(); let i = poss.len(); for v in vs.iter() { poss.push(Vec3::new(v.x as f32, v.y as f32, 0.)); } for v in 1..(vs.len()-1) { indices.extend_from_slice(&[i as u32, (i+v) as u32, (i+v+1) as u32]); cd.vertices.extend_from_slice(&[i, i+v, i+v+1]); } } let colors = vec![[0.; 4]; poss.len()]; let mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default()) // Add 4 vertices, each with its own position attribute (coordinate in // 3D space), for each of the corners of the parallelogram. .with_inserted_attribute( Mesh::ATTRIBUTE_POSITION, poss ) .with_inserted_attribute( Mesh::ATTRIBUTE_COLOR, colors.clone() ) .with_inserted_indices(Indices::U32(indices)); let mut cells_entities = Vec::with_capacity(cells.len()); cmds.spawn(( Mesh2d(meshes.add(mesh)), MeshMaterial2d(materials.add(ColorMaterial::default())), Transform::default(), Voronoi(voronoi), MapColors(colors), MeshNeedsUpdate(true), MapMarker )).with_children(|parent| { for cell in cells { let kind = cell.kind; let mut cmd = parent.spawn(cell); match kind { CellKind::Grass | CellKind::Forest => { cmd.insert((Wealth(0), Regeneration { last_update: Duration::ZERO, full_growth_duration: kind.regen_full_growth_duration() })); }, _ => {} } cmd.observe(| trigger: Trigger>, mut cells: Query<&mut Cell>, mut map_needs_update: Query<&mut MeshNeedsUpdate, With>, mut cmds: Commands, ca: Res, gt: Res, | { if trigger.duration > Duration::from_millis(200) { return } match *ca { CurrentAction::ChangeCell(ck) => { let mut cell = cells.get_mut(trigger.target).unwrap(); match ck { CellKind::Forest => match cell.kind { CellKind::Dirt | CellKind::Grass => { cmds.entity(trigger.target).insert((Wealth(0), Regeneration { last_update: gt.current, full_growth_duration: CellKind::Forest.regen_full_growth_duration() })); cell.kind = CellKind::Forest; map_needs_update.single_mut().0 = true; }, _ => {} }, CellKind::Grass => match cell.kind { CellKind::Dirt => { cmds.entity(trigger.target).insert((Wealth(0), Regeneration { last_update: gt.current, full_growth_duration: CellKind::Grass.regen_full_growth_duration() })); cell.kind = CellKind::Grass; map_needs_update.single_mut().0 = true; }, _ => {} } _ => {} } } _ => {} } }); cells_entities.push(cmd.id()); } }).insert(CellsEntities(cells_entities)); } fn update_map_mesh( cells: Query<(&Cell, Option<&Wealth>)>, mut map: Query<(&Mesh2d, &mut MapColors, &mut MeshNeedsUpdate), With>, mut meshes: ResMut> ) { let (mesh, mut cols, mut needs_update) = map.single_mut(); if needs_update.0 { if let Some(mesh) = meshes.get_mut(mesh) { let mut modified = false; for (cell, wealth) in cells.iter() { let col = cell.color(wealth.map(|w| w.0).unwrap_or_default()); for id in cell.vertices.iter() { modified = modified || cols.0[*id] != col; cols.0[*id] = col.clone(); } } if modified { mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, cols.0.clone()); } } needs_update.0 = false; } }