use bevy::{asset::RenderAssetUsages, prelude::*, render::mesh::{Indices, PrimitiveTopology}}; use noise::{Fbm, MultiFractal, NoiseFn, Perlin}; use rand::{Rng, SeedableRng}; use voronoice::{BoundingBox, Point, VoronoiBuilder}; pub struct Plugin; impl bevy::prelude::Plugin for Plugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup) .add_systems(Update, update) .insert_resource(ClearColor(Color::srgb(0., 0., 1.))); } } 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; pub const seed: u32 = 0; #[derive(Component)] struct Voronoi (voronoice::Voronoi); #[derive(Component)] struct MapMarker; #[derive(Component)] struct MapColors (Vec<[f32; 4]>); fn setup( mut cmds: Commands, mut meshes: ResMut>, mut materials: ResMut> ) { let mut rng = rand::rngs::SmallRng::seed_from_u64(seed 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); let moisture_noise = Fbm::::new(seed+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 as f32, m, 1., 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 mut 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 )); } fn update( cells: Query<&CellData>, 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(); for cd in cells.iter() { let col = cd.color(); for id in cd.vertices.iter() { cols.0[*id] = col.clone(); } } mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, cols.0.clone()); } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CellKind { Void, Sea, Beach, Forest, Dirt, Stone, Grass } #[derive(Debug, Component)] pub struct CellData { pub kind: CellKind, pub cid: usize, z: f32, pub moisture: f32, pub resource: f32, // How much resource there is (between 0 and 1) pub vertices: Vec } impl CellData { pub fn new(kind: CellKind, cell: usize, z: f32, moisture: f32, resource: f32, vertices: Vec) -> Self { Self { kind, cid: cell, z, moisture, resource, vertices } } // pub fn pos<'a>(&self, map: &'a Map) -> &'a Point { // &map.voronoi.sites()[self.cid] // } pub fn color(&self) -> [f32; 4] { // let mut rng = thread_rng(); // [rng.gen(), rng.gen(), rng.gen(), 1.] match self.kind { CellKind::Void => [0.; 4], CellKind::Sea => [0., 0., 1., 1.], CellKind::Beach => [0.82, 0.84, 0.51, 1.], CellKind::Forest => [0., 0.5 - (self.resource*0.4), 0., 1.], CellKind::Dirt => [0.53 - (self.resource*0.4), 0.38-(self.resource*0.4), 0.29-(self.resource*0.4), 1.], CellKind::Stone => [0.5, 0.5, 0.5, 1.], CellKind::Grass => [(136./255.) - (self.resource*0.4), (204./255.) - (self.resource*0.4), (59./255.) - (self.resource*0.4), 1.] } } pub fn update(&mut self) { // How much it get by day let recuperation_rate = match self.kind { CellKind::Void | CellKind::Sea | CellKind::Beach | CellKind::Dirt | CellKind::Stone => 0., CellKind::Forest => 1. / (100. * 365.25), // Let's say that a forest takes 100 years to mature CellKind::Grass => 1. / (7. * 7.) // Let's say that grass takes 7 weaks to reach its max }; self.resource = (self.resource + recuperation_rate).clamp(0., 1.); } pub fn set_resource(&mut self, val: f32, t: usize) { self.resource = val.clamp(0., 1.); if self.resource == 0. { match self.kind { CellKind::Forest => { self.kind = CellKind::Grass; self.resource = 1.; }, CellKind::Grass => { self.kind = CellKind::Dirt; self.resource = 0.5; }, CellKind::Beach => { self.kind = CellKind::Sea; }, _ => {} } } } } // pub struct Map { // pub voronoi: Voronoi, // pub cells_data: Vec, // pub seed: u32 // } // impl Map { // 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 = Self::REAL_HEIGHT * Self::REAL_WIDTH / Self::SIZE as f32; // pub const SIZE: usize = 10000; // pub fn new(seed: u32, t: usize) -> Self { // let mut rng = rand::rngs::SmallRng::seed_from_u64(seed as u64); // let mut sites = Vec::with_capacity(Self::SIZE); // for _ in 0..Self::SIZE { // sites.push(Point { x:rng.gen_range(-Self::WIDTH/2.0..Self::WIDTH/2.0) as f64, y:rng.gen_range(-Self::HEIGHT/2.0..Self::HEIGHT/2.0) as f64 }) // } // let voronoi = VoronoiBuilder::default() // .set_sites(sites) // .set_bounding_box(BoundingBox::new_centered(Self::WIDTH as f64, Self::HEIGHT as f64)) // .set_lloyd_relaxation_iterations(3) // .build() // .unwrap(); // let mut cells_data = Vec::with_capacity(Self::SIZE); // let z_noise = Fbm::::new(seed); // let moisture_noise = Fbm::::new(seed+1) // .set_frequency(2.); // for i in 0..Self::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 as f32, m, 1., t)); // } // Self { // voronoi, // cells_data, // seed // } // } // }