From 22f59b059e877dd4999539be25ca785947f7afe7 Mon Sep 17 00:00:00 2001 From: Arkitu <85173315+Arkitu@users.noreply.github.com> Date: Wed, 11 Jun 2025 21:16:18 +0200 Subject: [PATCH] better expansion (optimization) --- src/map.rs | 106 +++++++++++++++++++++++++++++------------------ src/map/cells.rs | 69 +++++++++++++++++++++++------- 2 files changed, 120 insertions(+), 55 deletions(-) diff --git a/src/map.rs b/src/map.rs index 581d837..d496c0c 100644 --- a/src/map.rs +++ b/src/map.rs @@ -26,21 +26,27 @@ use picking::*; pub struct Plugin; impl bevy::prelude::Plugin for Plugin { fn build(&self, app: &mut App) { - app.add_systems(Startup, setup) + app.add_plugins(cells::Plugin) + .add_systems(Startup, setup) .add_systems(PreUpdate, picking_backend.in_set(PickSet::Backend)) - .add_systems(Update, (update_map_mesh, cells_regeneration, expand)) + .add_systems(Update, update_map_mesh) .insert_resource(ClearColor(Color::srgb(0., 0., 1.))) - .insert_resource(Seed(thread_rng().gen())) - .add_plugins(MaterialPlugin::::default()); + .insert_resource(Seed(thread_rng().gen())); } } -pub const HEIGHT: f32 = 16.; -pub const WIDTH: f32 = 16.; +/// Determined empirically +pub const AVERAGE_NEIGHBORS_NUMBER: f64 = 6.; + +pub const HEIGHT: f64 = 16.; +pub const WIDTH: f64 = 16.; pub const REAL_HEIGHT: f32 = 8000.; pub const REAL_WIDTH: f32 = 8000.; -pub const CELL_AREA: f32 = REAL_HEIGHT * REAL_WIDTH / SIZE as f32; -pub const SIZE: usize = 100_000; +pub const CELL_AREA: f32 = REAL_HEIGHT * REAL_WIDTH / CELLS as f32; +pub const CELLS_TARGET_NUMBER: usize = 100_000; +pub const CHUNKS_RESOLUTION: usize = 2; +pub const CELLS_PER_CHUNK: usize = CELLS_TARGET_NUMBER / CHUNKS_RESOLUTION.pow(2); +pub const CELLS: usize = CELLS_PER_CHUNK * CHUNKS_RESOLUTION.pow(2); #[derive(Resource)] struct Seed(u32); @@ -68,24 +74,41 @@ fn setup( 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 mut sites = Vec::with_capacity(CELLS); + for chunk_x in 0..CHUNKS_RESOLUTION { + let min_x = WIDTH / 2. * ((2. * (chunk_x as f64) / (CHUNKS_RESOLUTION as f64)) - 1.); + let max_x = min_x + (WIDTH as f64 / CHUNKS_RESOLUTION as f64); + for chunk_y in 0..CHUNKS_RESOLUTION { + let min_y = HEIGHT / 2. * ((2. * (chunk_y as f64) / (CHUNKS_RESOLUTION as f64)) - 1.); + let max_y = min_y + (HEIGHT as f64 / CHUNKS_RESOLUTION as f64); + println!( + "[{},{}] x:[{}, {}], y:[{}, {}]", + chunk_x, chunk_y, min_x, max_x, min_y, max_y + ); + for _ in 0..CELLS_PER_CHUNK { + sites.push(Point { + x: rng.gen_range(min_x..max_x), + y: rng.gen_range(min_y..max_y), + }); + } + } } + // dbg!(sites.len()); + let voronoi = VoronoiBuilder::default() .set_sites(sites) - .set_bounding_box(BoundingBox::new_centered(WIDTH as f64, HEIGHT as f64)) + .set_bounding_box(BoundingBox::new_centered(WIDTH, HEIGHT)) .set_lloyd_relaxation_iterations(3) .build() .unwrap(); - let mut cells = Vec::with_capacity(SIZE); + + let mut cells = Vec::with_capacity(CELLS); let z_noise = Fbm::::new(seed.0); let moisture_noise = Fbm::::new(seed.0 + 1).set_frequency(2.); - for i in 0..SIZE { + let mut res = 0; + for i in 0..CELLS { let c = voronoi.cell(i); + res += c.iter_neighbors().count(); let site = c.site_position(); let z = get_altitude(&z_noise, &[site.x as f32, site.y as f32]); let _m = ( @@ -108,6 +131,7 @@ fn setup( vertices: vec![], }); } + dbg!(res as f64 / CELLS as f64); let mut poss = Vec::new(); let mut indices = Vec::new(); @@ -181,27 +205,27 @@ fn setup( MapMeshColors(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(), - }, - )); - } - _ => {} + )); + + for cell in cells { + let kind = cell.kind; + let mut cmd = cmds.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(self::cells::on_click); - cells_entities.push(cmd.id()); + _ => {} } - }); + cmd.observe(self::cells::on_click); + cells_entities.push(cmd.id()); + } + cmds.insert_resource(Voronoi(voronoi)); cmds.insert_resource(CellsEntities(cells_entities)); } @@ -219,10 +243,12 @@ fn update_map_mesh( for (cell, wealth) in cells.iter() { // let col: [f32; 4] = [rng.gen(), rng.gen(), rng.gen(), 1.]; 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(); - } + modified = modified || cols.0[cell.voronoi_id] != col; + cols.0[cell.voronoi_id] = col; + // 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()); diff --git a/src/map/cells.rs b/src/map/cells.rs index 3aaf6fc..b80a3a8 100644 --- a/src/map/cells.rs +++ b/src/map/cells.rs @@ -1,13 +1,31 @@ use std::time::Duration; -use bevy::prelude::*; +use bevy::{prelude::*, time::common_conditions::on_timer}; use rand::{seq::IteratorRandom, thread_rng, Rng}; use crate::{time::GameTime, ui::CurrentAction}; -use super::{animals::Animal, AnimalKind, CellsEntities, MapMarker, MeshNeedsUpdate, Voronoi}; +use super::{ + animals::Animal, AnimalKind, CellsEntities, MapMarker, MeshNeedsUpdate, Voronoi, + AVERAGE_NEIGHBORS_NUMBER, CELL_AREA, +}; pub mod material; +use material::CellMaterial; + +pub struct Plugin; +impl bevy::prelude::Plugin for Plugin { + fn build(&self, app: &mut App) { + app.add_systems( + Update, + ( + cells_regeneration.run_if(on_timer(Duration::from_secs(1))), + expand.run_if(on_timer(Duration::from_secs(1))), + ), + ) + .add_plugins(MaterialPlugin::::default()); + } +} #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CellKind { @@ -79,6 +97,7 @@ pub struct Regeneration { pub full_growth_duration: Duration, } +const STEP: u8 = u8::MAX / 4; pub fn cells_regeneration( mut cells: Query<(&mut Regeneration, &mut Wealth)>, mut map_needs_update: Query<&mut MeshNeedsUpdate, With>, @@ -86,10 +105,15 @@ pub fn cells_regeneration( ) { let mut map_needs_update = map_needs_update.single_mut(); for (mut regen, mut wealth) in cells.iter_mut() { - if gt.current - regen.last_update > regen.full_growth_duration / u8::MAX as u32 { - regen.last_update = gt.current; + while gt.current - regen.last_update > regen.full_growth_duration / u8::MAX as u32 { + regen.last_update = gt + .current + .min(regen.last_update + (regen.full_growth_duration / u8::MAX as u32)); wealth.0 = wealth.0.saturating_add(1); - map_needs_update.0 = true; + // Not update map each time for optimization + if wealth.0 % STEP == 0 { + map_needs_update.0 = true; + } } } } @@ -113,28 +137,43 @@ pub fn expand( wealth.unwrap().0 as f64 / 255. }; if random.gen_bool( - (t.elapsed_secs_f64() * (gt.speed as f64) * wealth / (60. * 60. * 24. * 30.)) - .min(1.), + dbg!( + t.elapsed_secs_f64() * (gt.speed as f64) * wealth * 1e-7 + / (CELL_AREA as f64).sqrt() + ) + .min(1.), ) { - // Let say that grass takes 1 months to expand + // "Grass can expand into adjacent dirt at a rate of 2.5 to 7.5 cm (1 to 3 inches) per month.", DeepSeek + // With some (way too complicated and probably false) computation, we get for a good grass an extension rate of (1e-7 / sqrt(cell_area)) cells per seconds let target = voronoi .0 .cell(cell.voronoi_id) .iter_neighbors() .choose(&mut random) .unwrap(); - changes.push((target, CellKind::Grass)); + changes.extend( + voronoi + .0 + .cell(cell.voronoi_id) + .iter_neighbors() + .map(|t| (t, CellKind::Grass)), + ); } } if cell.kind == CellKind::Forest { if random.gen_bool( - (t.elapsed_secs_f64() - * (gt.speed as f64) - * (wealth.unwrap().0 as f64 / 255.).sqrt() - / (60. * 60. * 24. * 365. * 5.)) - .min(1.), + dbg!( + t.elapsed_secs_f64() + * (gt.speed as f64) + * (wealth.unwrap().0 as f64 / 255.).sqrt() + * 5.6e-7 + / (CELL_AREA as f64).sqrt() + * AVERAGE_NEIGHBORS_NUMBER + ) + .min(1.), ) { - // Let say that forest takes 5 years to expand + // "Forests can expand at a rate of 1 to 10 meters per year", DeepSeek + // Same computations as above : (5.6e-7 / sqrt(cell_area)) cells per seconds let target = voronoi .0 .cell(cell.voronoi_id)