use bevy::{asset::RenderAssetUsages, prelude::*, render::mesh::{Indices, PrimitiveTopology}}; use noise::{Fbm, MultiFractal, NoiseFn, Perlin}; use rand::{Rng, SeedableRng}; use voronoice::{BoundingBox, Point, VoronoiBuilder}; mod cells; 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) .insert_resource(ClearColor(Color::srgb(0., 0., 1.))) .insert_resource(Seed(0)); } } 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)] struct Voronoi (voronoice::Voronoi); #[derive(Component)] pub struct MapMarker; #[derive(Component)] struct MapColors (Vec<[f32; 4]>); 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::Grass } 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)); cmds.spawn(( Mesh2d(meshes.add(mesh)), MeshMaterial2d(materials.add(ColorMaterial::default())), Transform::default(), Voronoi(voronoi), MapColors(colors), MapMarker )).with_children(|parent| { for cd in cells_data { match cd.kind { CellKind::Forest | CellKind::Grass => parent.spawn((cd, LastUpdate(0))), _ => parent.spawn(cd) }; } }); } #[derive(Component)] pub struct LastUpdate(usize); fn update_cells( mut cells: Query<(&mut CellData, &mut LastUpdate)>, mut map: Query<(&Mesh2d, &mut MapColors), With>, mut meshes: ResMut> ) { let (mesh, mut cols) = map.single_mut(); if let Some(mesh) = meshes.get_mut(mesh) { // let cols = mesh.attribute_mut(Mesh::ATTRIBUTE_COLOR).unwrap(); let mut modified = false; 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); modified = true; } // cd.update(); let col = cd.color(); for id in cd.vertices.iter() { cols.0[*id] = col.clone(); } } if modified { mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, cols.0.clone()); } } }