use std::time::Duration; use bevy::{asset::RenderAssetUsages, picking::PickSet, prelude::*, render::mesh::{Indices, PrimitiveTopology}, utils::HashMap}; 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 struct Plugin; impl bevy::prelude::Plugin for Plugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup) .insert_resource(Time::::from_seconds(0.25)) // Time for a day .add_systems(FixedUpdate, update_cells) .add_systems(PreUpdate, picking_backend.in_set(PickSet::Backend)) .add_systems(Update, update_map_mesh) .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_data = 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_data.push(CellData::new(k, i, (z*255.) as u8, (m*255.) as u8, 0, vec![])); } let mut poss = Vec::new(); let mut colors = Vec::new(); let mut indices = Vec::new(); for (c, cd) in voronoi.iter_cells().zip(cells_data.iter_mut()).filter(|(_,cd)| cd.kind != CellKind::Forest) { let color = cd.color(); // if c.site() == selected_tile { // color[0] = (color[0]+0.4).clamp(0., 1.); // color[1] = (color[1]+0.4).clamp(0., 1.); // color[2] = (color[2]+0.4).clamp(0., 1.); // } 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.));// [v.x as f32, v.y as f32, 0.]); // poss.push(Vertex::new_col([v.x as f32, v.y as f32], color, 1)); colors.push(color); } 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 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_data.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 cd in cells_data { let mut cmd = parent.spawn((cd, LastUpdate(0))); cmd.observe(|trigger: Trigger>, mut cells: Query<&mut CellData>, mut map_needs_update: Query<&mut MeshNeedsUpdate, With>| { if trigger.duration > Duration::from_millis(100) { return } let mut cd = cells.get_mut(trigger.target).unwrap(); match cd.kind { CellKind::Dirt | CellKind::Grass => { cd.kind = CellKind::Forest; map_needs_update.single_mut().0 = true; }, _ => {} } dbg!(trigger.duration); }); cells_entities.push(cmd.id()); } }).insert(CellsEntities(cells_entities)); } #[derive(Component)] pub struct LastUpdate(usize); fn update_cells( mut cells: Query<(&mut CellData, &mut LastUpdate)>, mut map_needs_update: Query<&mut MeshNeedsUpdate, With> ) { let mut map_needs_update = map_needs_update.single_mut(); for (mut cd, mut lu) in cells.iter_mut() { lu.0 += 1; if lu.0 > match cd.kind { CellKind::Void | CellKind::Sea | CellKind::Beach | CellKind::Dirt | CellKind::Stone => usize::MAX, CellKind::Forest => 100*365/4, // Let's say that a forest takes 100 years to mature CellKind::Grass => 7*7/4 // Let's say that grass takes 7 weaks to reach its max } { lu.0 = 0; cd.resource = (cd.resource + 1).clamp(0, 4); map_needs_update.0 = true; } } } fn update_map_mesh( cells: Query<&CellData>, 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 cd in cells.iter() { let col = cd.color(); for id in cd.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; } }