2025-01-11 16:44:42 +00:00
|
|
|
use std::time::Duration;
|
|
|
|
|
2025-01-23 20:41:32 +00:00
|
|
|
use bevy::{asset::RenderAssetUsages, picking::PickSet, prelude::*, render::mesh::{Indices, PrimitiveTopology}};
|
2024-12-22 19:33:27 +00:00
|
|
|
use noise::{Fbm, MultiFractal, NoiseFn, Perlin};
|
2025-01-11 14:09:32 +00:00
|
|
|
use rand::{thread_rng, Rng, SeedableRng};
|
2024-12-22 19:33:27 +00:00
|
|
|
use voronoice::{BoundingBox, Point, VoronoiBuilder};
|
|
|
|
|
2024-12-23 21:07:31 +00:00
|
|
|
mod cells;
|
2025-01-09 19:56:50 +00:00
|
|
|
mod picking;
|
|
|
|
use picking::*;
|
2024-12-23 21:07:31 +00:00
|
|
|
use cells::*;
|
2025-01-23 20:41:32 +00:00
|
|
|
pub use cells::CellKind;
|
2024-12-23 21:07:31 +00:00
|
|
|
|
2025-01-23 20:41:32 +00:00
|
|
|
use crate::{time::GameTime, ui::CurrentAction};
|
2025-01-19 21:40:19 +00:00
|
|
|
|
2024-12-22 19:33:27 +00:00
|
|
|
pub struct Plugin;
|
|
|
|
impl bevy::prelude::Plugin for Plugin {
|
|
|
|
fn build(&self, app: &mut App) {
|
|
|
|
app.add_systems(Startup, setup)
|
2025-01-09 19:56:50 +00:00
|
|
|
.add_systems(PreUpdate, picking_backend.in_set(PickSet::Backend))
|
2025-01-20 18:53:48 +00:00
|
|
|
.add_systems(Update, (update_map_mesh, cells_regeneration, expand))
|
2024-12-24 17:59:27 +00:00
|
|
|
.insert_resource(ClearColor(Color::srgb(0., 0., 1.)))
|
2025-01-11 14:09:32 +00:00
|
|
|
.insert_resource(Seed(thread_rng().gen()));
|
2024-12-22 19:33:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2024-12-24 17:59:27 +00:00
|
|
|
|
|
|
|
#[derive(Resource)]
|
|
|
|
struct Seed(u32);
|
2024-12-22 19:33:27 +00:00
|
|
|
|
2024-12-22 21:02:45 +00:00
|
|
|
#[derive(Component)]
|
2025-01-09 19:56:50 +00:00
|
|
|
pub struct Voronoi (voronoice::Voronoi);
|
2024-12-22 19:33:27 +00:00
|
|
|
|
2024-12-22 21:02:45 +00:00
|
|
|
#[derive(Component)]
|
2024-12-24 17:59:27 +00:00
|
|
|
pub struct MapMarker;
|
2024-12-22 21:02:45 +00:00
|
|
|
|
|
|
|
#[derive(Component)]
|
|
|
|
struct MapColors (Vec<[f32; 4]>);
|
|
|
|
|
2025-01-09 19:56:50 +00:00
|
|
|
#[derive(Component)]
|
|
|
|
pub struct CellsEntities (Vec<Entity>);
|
|
|
|
|
2025-01-15 18:04:22 +00:00
|
|
|
#[derive(Component)]
|
|
|
|
pub struct MeshNeedsUpdate(bool);
|
|
|
|
|
2024-12-22 19:33:27 +00:00
|
|
|
fn setup(
|
|
|
|
mut cmds: Commands,
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
2024-12-24 17:59:27 +00:00
|
|
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
|
|
seed: Res<Seed>
|
2024-12-22 19:33:27 +00:00
|
|
|
) {
|
2024-12-24 17:59:27 +00:00
|
|
|
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed.0 as u64);
|
2024-12-22 19:33:27 +00:00
|
|
|
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();
|
2025-01-19 21:40:19 +00:00
|
|
|
let mut cells = Vec::with_capacity(SIZE);
|
2024-12-24 17:59:27 +00:00
|
|
|
let z_noise = Fbm::<Perlin>::new(seed.0);
|
|
|
|
let moisture_noise = Fbm::<Perlin>::new(seed.0+1)
|
2024-12-22 19:33:27 +00:00
|
|
|
.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 {
|
2025-01-11 14:09:32 +00:00
|
|
|
CellKind::Dirt
|
2024-12-22 19:33:27 +00:00
|
|
|
} else {
|
|
|
|
CellKind::Stone
|
|
|
|
};
|
2025-01-19 21:40:19 +00:00
|
|
|
cells.push(Cell {
|
|
|
|
kind: k,
|
|
|
|
voronoi_id: i,
|
|
|
|
altitude: (z*255.) as u8,
|
|
|
|
vertices: vec![]
|
|
|
|
});
|
2024-12-22 19:33:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let mut poss = Vec::new();
|
|
|
|
let mut indices = Vec::new();
|
|
|
|
|
2025-01-19 21:40:19 +00:00
|
|
|
for (c, cd) in voronoi.iter_cells().zip(cells.iter_mut()).filter(|(_,cd)| cd.kind != CellKind::Forest) {
|
2024-12-22 19:33:27 +00:00
|
|
|
let vs = c.iter_vertices().collect::<Vec<_>>();
|
2024-12-22 21:02:45 +00:00
|
|
|
let i = poss.len();
|
2024-12-22 19:33:27 +00:00
|
|
|
for v in vs.iter() {
|
2025-01-19 21:40:19 +00:00
|
|
|
poss.push(Vec3::new(v.x as f32, v.y as f32, 0.));
|
2024-12-22 19:33:27 +00:00
|
|
|
}
|
2024-12-22 21:02:45 +00:00
|
|
|
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]);
|
2024-12-22 19:33:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-19 21:40:19 +00:00
|
|
|
let colors = vec![[0.; 4]; poss.len()];
|
2024-12-22 19:33:27 +00:00
|
|
|
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,
|
2024-12-22 21:02:45 +00:00
|
|
|
colors.clone()
|
2024-12-22 19:33:27 +00:00
|
|
|
)
|
|
|
|
.with_inserted_indices(Indices::U32(indices));
|
|
|
|
|
2025-01-19 21:40:19 +00:00
|
|
|
let mut cells_entities = Vec::with_capacity(cells.len());
|
2024-12-22 19:33:27 +00:00
|
|
|
cmds.spawn((
|
|
|
|
Mesh2d(meshes.add(mesh)),
|
|
|
|
MeshMaterial2d(materials.add(ColorMaterial::default())),
|
2024-12-22 21:02:45 +00:00
|
|
|
Transform::default(),
|
|
|
|
Voronoi(voronoi),
|
|
|
|
MapColors(colors),
|
2025-01-15 18:04:22 +00:00
|
|
|
MeshNeedsUpdate(true),
|
2024-12-22 21:02:45 +00:00
|
|
|
MapMarker
|
2024-12-24 17:59:27 +00:00
|
|
|
)).with_children(|parent| {
|
2025-01-19 21:40:19 +00:00
|
|
|
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<Pointer<Click>>,
|
|
|
|
mut cells: Query<&mut Cell>,
|
|
|
|
mut map_needs_update: Query<&mut MeshNeedsUpdate, With<MapMarker>>,
|
|
|
|
mut cmds: Commands,
|
2025-01-23 20:41:32 +00:00
|
|
|
ca: Res<CurrentAction>,
|
|
|
|
gt: Res<GameTime>,
|
2025-01-19 21:40:19 +00:00
|
|
|
| {
|
|
|
|
if trigger.duration > Duration::from_millis(200) {
|
2025-01-11 16:44:42 +00:00
|
|
|
return
|
|
|
|
}
|
2025-01-23 20:41:32 +00:00
|
|
|
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;
|
|
|
|
},
|
|
|
|
_ => {}
|
|
|
|
},
|
2025-02-20 18:30:25 +00:00
|
|
|
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;
|
|
|
|
},
|
|
|
|
_ => {}
|
|
|
|
}
|
2025-01-23 20:41:32 +00:00
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
2025-01-11 14:09:32 +00:00
|
|
|
_ => {}
|
|
|
|
}
|
2025-01-23 20:41:32 +00:00
|
|
|
|
2025-01-09 19:56:50 +00:00
|
|
|
});
|
|
|
|
cells_entities.push(cmd.id());
|
2024-12-24 17:59:27 +00:00
|
|
|
}
|
2025-01-09 19:56:50 +00:00
|
|
|
}).insert(CellsEntities(cells_entities));
|
|
|
|
|
2024-12-22 21:02:45 +00:00
|
|
|
}
|
2024-12-22 19:33:27 +00:00
|
|
|
|
2025-01-11 14:09:32 +00:00
|
|
|
fn update_map_mesh(
|
2025-01-19 21:40:19 +00:00
|
|
|
cells: Query<(&Cell, Option<&Wealth>)>,
|
2025-01-15 18:04:22 +00:00
|
|
|
mut map: Query<(&Mesh2d, &mut MapColors, &mut MeshNeedsUpdate), With<MapMarker>>,
|
2024-12-22 21:02:45 +00:00
|
|
|
mut meshes: ResMut<Assets<Mesh>>
|
|
|
|
) {
|
2025-01-15 18:04:22 +00:00
|
|
|
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;
|
2025-01-19 21:40:19 +00:00
|
|
|
for (cell, wealth) in cells.iter() {
|
|
|
|
let col = cell.color(wealth.map(|w| w.0).unwrap_or_default());
|
|
|
|
for id in cell.vertices.iter() {
|
2025-01-15 18:04:22 +00:00
|
|
|
modified = modified || cols.0[*id] != col;
|
|
|
|
cols.0[*id] = col.clone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if modified {
|
|
|
|
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, cols.0.clone());
|
2024-12-22 21:02:45 +00:00
|
|
|
}
|
|
|
|
}
|
2025-01-15 18:04:22 +00:00
|
|
|
needs_update.0 = false;
|
2024-12-22 21:02:45 +00:00
|
|
|
}
|
2024-12-23 21:07:31 +00:00
|
|
|
}
|