From b40d19ad0eb9e77845865fb0bb5413e9ebcd766f Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 25 Jun 2019 16:59:09 +0100 Subject: [PATCH] Added settlement generation --- world/examples/city.rs | 30 +++++++++++ world/examples/turb.rs | 47 ++++++++++++++++++ world/examples/view.rs | 7 +-- world/src/block/mod.rs | 5 +- world/src/column/mod.rs | 63 ++++++++++++++++------- world/src/lib.rs | 2 +- world/src/sim/location.rs | 22 ++++----- world/src/sim/mod.rs | 99 ++++++++++++++++++++++--------------- world/src/sim/settlement.rs | 95 +++++++++++++++++++++++++++++++++++ 9 files changed, 292 insertions(+), 78 deletions(-) create mode 100644 world/examples/city.rs create mode 100644 world/examples/turb.rs create mode 100644 world/src/sim/settlement.rs diff --git a/world/examples/city.rs b/world/examples/city.rs new file mode 100644 index 0000000000..6bd9af95af --- /dev/null +++ b/world/examples/city.rs @@ -0,0 +1,30 @@ +use std::ops::{Add, Mul, Sub}; +use vek::*; +use veloren_world::sim::Settlement; +use rand::thread_rng; + +const W: usize = 640; +const H: usize = 480; + +fn main() { + let mut win = + minifb::Window::new("City Viewer", W, H, minifb::WindowOptions::default()).unwrap(); + + let settlement = Settlement::generate(&mut thread_rng()); + + while win.is_open() { + let mut buf = vec![0; W * H]; + + for i in 0..W { + for j in 0..H { + let pos = Vec2::new(i as f32, j as f32) * 0.002; + + let seed = settlement.get_at(pos).map(|b| b.seed).unwrap_or(0); + + buf[j * W + i] = u32::from_le_bytes([(seed >> 0) as u8, (seed >> 8) as u8, (seed >> 16) as u8, (seed >> 24) as u8]); + } + } + + win.update_with_buffer(&buf).unwrap(); + } +} diff --git a/world/examples/turb.rs b/world/examples/turb.rs new file mode 100644 index 0000000000..419d70756e --- /dev/null +++ b/world/examples/turb.rs @@ -0,0 +1,47 @@ +use std::ops::{Add, Mul, Sub}; +use vek::*; +use veloren_world::sim::Settlement; +use rand::thread_rng; +use noise::{Seedable, NoiseFn, SuperSimplex}; + +const W: usize = 640; +const H: usize = 640; + +fn main() { + let mut win = + minifb::Window::new("Turb", W, H, minifb::WindowOptions::default()).unwrap(); + + let nz_x = SuperSimplex::new().set_seed(0); + let nz_y = SuperSimplex::new().set_seed(1); + + let mut time = 0.0f64; + while win.is_open() { + let mut buf = vec![0; W * H]; + + for i in 0..W { + for j in 0..H { + let pos = Vec2::new(i as f64 / W as f64, j as f64 / H as f64) * 0.5 - 0.25; + + let pos = pos * 10.0; + + let pos = (0..10) + .fold(pos, |pos, _| pos.map(|e| e.powf(3.0) - 1.0)); + + let val = if pos + .map(|e| e.abs() < 0.5) + .reduce_and() + { + 1.0f32 + } else { + 0.0 + }; + + buf[j * W + i] = u32::from_le_bytes([(val.max(0.0).min(1.0) * 255.0) as u8; 4]); + } + } + + win.update_with_buffer(&buf).unwrap(); + + time += 1.0 / 60.0; + } +} diff --git a/world/examples/view.rs b/world/examples/view.rs index 2fa077389e..c1f52ec8e6 100644 --- a/world/examples/view.rs +++ b/world/examples/view.rs @@ -35,12 +35,7 @@ fn main() { .unwrap_or((0, None)); let loc_color = location - .map(|l| { - ( - l.loc.name().bytes().nth(0).unwrap() * 17, - l.loc.name().bytes().nth(1).unwrap() * 17, - ) - }) + .map(|l| (l.loc_idx as u8 * 17, l.loc_idx as u8 * 13)) .unwrap_or((0, 0)); buf[j * W + i] = u32::from_le_bytes([loc_color.0, loc_color.1, alt, alt]); diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index f48ed9f5db..02e12d5ff5 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -52,7 +52,7 @@ impl<'a> BlockGen<'a> { cache, Vec2::from(*cliff_pos), ) { - Some(cliff_sample) if cliff_sample.cliffs => { + Some(cliff_sample) if cliff_sample.cliffs && cliff_sample.spawn_rate > 0.5 => { let cliff_pos3d = Vec3::from(*cliff_pos); let height = RandomField::new(seed + 1).get(cliff_pos3d) % 48; @@ -299,7 +299,8 @@ impl<'a> SamplerMut for BlockGen<'a> { Some(tree_sample) if tree_sample.tree_density > 0.5 + (*tree_seed as f32 / 1000.0).fract() * 0.2 - && tree_sample.alt > tree_sample.water_level => + && tree_sample.alt > tree_sample.water_level + && tree_sample.spawn_rate > 0.5 => { let cliff_height = Self::get_cliff_height( column_gen, diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index f2cf975529..82d59dbb4e 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -50,6 +50,7 @@ impl<'a> Sampler for ColumnGen<'a> { let dryness = sim.get_interpolated(wpos, |chunk| chunk.dryness)?; let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?; let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?; + let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?; let sim_chunk = sim.get(chunk_pos)?; @@ -135,25 +136,32 @@ impl<'a> Sampler for ColumnGen<'a> { ); // Work out if we're on a path or near a town - let near_0 = sim_chunk - .location - .as_ref() - .map(|l| l.near[0].block_pos) - .unwrap_or(Vec2::zero()) - .map(|e| e as f32); - let near_1 = sim_chunk - .location - .as_ref() - .map(|l| l.near[1].block_pos) - .unwrap_or(Vec2::zero()) - .map(|e| e as f32); + let dist_to_path = match &sim_chunk.location { + Some(loc) => { + let this_loc = &sim.locations[loc.loc_idx]; + this_loc.neighbours + .iter() + .map(|j| { + let other_loc = &sim.locations[*j]; - let dist_to_path = (0.0 + (near_1.y - near_0.y) * wposf_turb.x as f32 - - (near_1.x - near_0.x) * wposf_turb.y as f32 - + near_1.x * near_0.y - - near_0.x * near_1.y) - .abs() - .div(near_0.distance(near_1)); + // Find the two location centers + let near_0 = this_loc.center.map(|e| e as f32); + let near_1 = other_loc.center.map(|e| e as f32); + + // Calculate distance to path between them + (0.0 + (near_1.y - near_0.y) * wposf_turb.x as f32 + - (near_1.x - near_0.x) * wposf_turb.y as f32 + + near_1.x * near_0.y + - near_0.x * near_1.y) + .abs() + .div(near_0.distance(near_1)) + }) + .filter(|x| x.is_finite()) + .min_by(|a, b| a.partial_cmp(b).unwrap()) + .unwrap_or(f32::INFINITY) + }, + None => f32::INFINITY, + }; let on_path = dist_to_path < 5.0; // || near_0.distance(wposf_turb.map(|e| e as f32)) < 150.0; @@ -163,6 +171,23 @@ impl<'a> Sampler for ColumnGen<'a> { (alt, ground) }; + // Cities + let building = match &sim_chunk.location { + Some(loc) => { + let loc = &sim.locations[loc.loc_idx]; + let rpos = wposf.map2(loc.center, |a, b| a as f32 - b as f32) / 256.0 + 0.5; + + if rpos.map(|e| e >= 0.0 && e < 1.0).reduce_and() { + (loc.settlement.get_at(rpos).map(|b| b.seed % 20 + 10).unwrap_or(0)) as f32 + } else { + 0.0 + } + }, + None => 0.0, + }; + + let alt = alt + building; + // Caves let cave_at = |wposf: Vec2| { (sim.gen_ctx.cave_0_nz.get( @@ -228,6 +253,7 @@ impl<'a> Sampler for ColumnGen<'a> { cliff_hill, close_cliffs: sim.gen_ctx.cliff_gen.get(wpos), temp, + spawn_rate, location: sim_chunk.location.as_ref(), }) } @@ -251,5 +277,6 @@ pub struct ColumnSample<'a> { pub cliff_hill: f32, pub close_cliffs: [(Vec2, u32); 9], pub temp: f32, + pub spawn_rate: f32, pub location: Option<&'a LocationInfo>, } diff --git a/world/src/lib.rs b/world/src/lib.rs index 36af8e2536..515ffd13da 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -74,7 +74,7 @@ impl World { None => return TerrainChunk::new(0, water, air, TerrainChunkMeta::void()), }; - let meta = TerrainChunkMeta::new(sim_chunk.get_name(), sim_chunk.get_biome()); + let meta = TerrainChunkMeta::new(sim_chunk.get_name(&self.sim), sim_chunk.get_biome()); let mut chunk = TerrainChunk::new(base_z - 8, stone, air, meta); diff --git a/world/src/sim/location.rs b/world/src/sim/location.rs index 1acd7d45c3..916ffba554 100644 --- a/world/src/sim/location.rs +++ b/world/src/sim/location.rs @@ -1,27 +1,25 @@ use rand::Rng; use vek::*; - -#[derive(Copy, Clone, Debug)] -pub enum LocationKind { - Settlement, - Wildnerness, -} +use fxhash::FxHashSet; +use super::Settlement; #[derive(Clone, Debug)] pub struct Location { - name: String, - center: Vec2, - kind: LocationKind, - kingdom: Option, + pub(crate) name: String, + pub(crate) center: Vec2, + pub(crate) kingdom: Option, + pub(crate) neighbours: FxHashSet, + pub(crate) settlement: Settlement, } impl Location { - pub fn generate(center: Vec2, rng: &mut R) -> Self { + pub fn generate(center: Vec2, rng: &mut impl Rng) -> Self { Self { name: generate_name(rng), center, - kind: LocationKind::Wildnerness, kingdom: None, + neighbours: FxHashSet::default(), + settlement: Settlement::generate(rng), } } diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 2dbd759815..5a237c6460 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1,7 +1,9 @@ mod location; +mod settlement; // Reexports pub use self::location::Location; +pub use self::settlement::Settlement; use crate::{ all::ForestKind, @@ -49,6 +51,8 @@ pub(crate) struct GenCtx { pub struct WorldSim { pub seed: u32, pub(crate) chunks: Vec, + pub(crate) locations: Vec, + pub(crate) gen_ctx: GenCtx, pub rng: XorShiftRng, } @@ -91,6 +95,7 @@ impl WorldSim { let mut this = Self { seed, chunks, + locations: Vec::new(), gen_ctx, rng: XorShiftRng::from_seed([ (seed >> 0) as u8, @@ -113,7 +118,6 @@ impl WorldSim { }; this.seed_elements(); - this.simulate(0); this } @@ -126,7 +130,8 @@ impl WorldSim { let grid_size = WORLD_SIZE / cell_size; let loc_count = 250; - let mut locations = vec![None; grid_size.product()]; + let mut loc_grid = vec![None; grid_size.product()]; + let mut locations = Vec::new(); // Seed the world with some locations for _ in 0..loc_count { @@ -134,13 +139,35 @@ impl WorldSim { self.rng.gen::() % grid_size.x, self.rng.gen::() % grid_size.y, ); - let wpos = (cell_pos * cell_size) + let wpos = (cell_pos * cell_size + cell_size / 2) .map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| { - e as i32 * sz as i32 + e as i32 * sz as i32 + sz as i32 / 2 }); - locations[cell_pos.y * grid_size.x + cell_pos.x] = - Some(Arc::new(Location::generate(wpos, &mut rng))); + locations.push(Location::generate(wpos, &mut rng)); + + loc_grid[cell_pos.y * grid_size.x + cell_pos.x] = Some(locations.len() - 1); + } + + // Find neighbours + let mut loc_clone = locations + .iter() + .map(|l| l.center) + .enumerate() + .collect::>(); + for i in 0..locations.len() { + let pos = locations[i].center; + + loc_clone.sort_by_key(|(_, l)| l.distance_squared(pos)); + + loc_clone + .iter() + .skip(1) + .take(2) + .for_each(|(j, _)| { + locations[i].neighbours.insert(*j); + locations[*j].neighbours.insert(i); + }); } // Simulate invasion! @@ -148,13 +175,13 @@ impl WorldSim { for _ in 0..invasion_cycles { for i in 0..grid_size.x { for j in 0..grid_size.y { - if locations[j * grid_size.x + i].is_none() { + if loc_grid[j * grid_size.x + i].is_none() { const R_COORDS: [i32; 5] = [-1, 0, 1, 0, -1]; let idx = self.rng.gen::() % 4; let loc = Vec2::new(i as i32 + R_COORDS[idx], j as i32 + R_COORDS[idx + 1]) .map(|e| e as usize); - locations[j * grid_size.x + i] = locations + loc_grid[j * grid_size.x + i] = loc_grid .get(loc.y * grid_size.x + loc.x) .cloned() .flatten(); @@ -168,6 +195,7 @@ impl WorldSim { for i in 0..WORLD_SIZE.x { for j in 0..WORLD_SIZE.y { let chunk_pos = Vec2::new(i as i32, j as i32); + let block_pos = Vec2::new(chunk_pos.x * TerrainChunkSize::SIZE.x as i32, chunk_pos.y * TerrainChunkSize::SIZE.y as i32); let cell_pos = Vec2::new(i / cell_size, j / cell_size); // Find the distance to each region @@ -188,43 +216,28 @@ impl WorldSim { near.sort_by(|a, b| a.dist.partial_cmp(&b.dist).unwrap()); let nearest_cell_pos = near[0].chunk_pos.map(|e| e as usize) / cell_size; - self.get_mut(chunk_pos).unwrap().location = locations + self.get_mut(chunk_pos).unwrap().location = loc_grid .get(nearest_cell_pos.y * grid_size.x + nearest_cell_pos.x) .cloned() .unwrap_or(None) - .map(|loc| LocationInfo { loc, near }); - } - } + .map(|loc_idx| LocationInfo { loc_idx, near }); - self.rng = rng; - } - - pub fn simulate(&mut self, cycles: usize) { - let mut rng = self.rng.clone(); - - for _ in 0..cycles { - for i in 0..WORLD_SIZE.x as i32 { - for j in 0..WORLD_SIZE.y as i32 { - let pos = Vec2::new(i, j); - - let location = self.get(pos).unwrap().location.clone(); - - let rpos = - Vec2::new(rng.gen::(), rng.gen::()).map(|e| e.abs() % 3 - 1); - - if let Some(other) = &mut self.get_mut(pos + rpos) { - if other.location.is_none() - && rng.gen::() > other.chaos * 1.5 - && other.alt > CONFIG.sea_level - { - other.location = location; - } - } + let town_size = 200; + let in_town = self + .get(chunk_pos) + .unwrap() + .location + .as_ref() + .map(|l| locations[l.loc_idx].center.distance_squared(block_pos) < town_size * town_size) + .unwrap_or(false); + if in_town { + self.get_mut(chunk_pos).unwrap().spawn_rate = 0.0; } } } self.rng = rng; + self.locations = locations; } pub fn get(&self, chunk_pos: Vec2) -> Option<&SimChunk> { @@ -312,6 +325,7 @@ pub struct SimChunk { pub near_cliffs: bool, pub tree_density: f32, pub forest_kind: ForestKind, + pub spawn_rate: f32, pub location: Option, } @@ -325,7 +339,7 @@ pub struct RegionInfo { #[derive(Clone)] pub struct LocationInfo { - pub loc: Arc, + pub loc_idx: usize, pub near: Vec, } @@ -437,6 +451,7 @@ impl SimChunk { ForestKind::SnowPine } }, + spawn_rate: 1.0, location: None, } } @@ -454,8 +469,14 @@ impl SimChunk { .max(CONFIG.sea_level + 2.0) } - pub fn get_name(&self) -> Option { - self.location.as_ref().map(|l| l.loc.name().to_string()) + pub fn get_name(&self, world: &WorldSim) -> Option { + if let Some(loc) = &self.location { + Some(world.locations[loc.loc_idx] + .name() + .to_string()) + } else { + None + } } pub fn get_biome(&self) -> BiomeKind { diff --git a/world/src/sim/settlement.rs b/world/src/sim/settlement.rs new file mode 100644 index 0000000000..946193aef8 --- /dev/null +++ b/world/src/sim/settlement.rs @@ -0,0 +1,95 @@ +use rand::Rng; +use vek::*; + +#[derive(Clone, Debug)] +pub struct Settlement { + lot: Lot, +} + +impl Settlement { + pub fn generate(rng: &mut impl Rng) -> Self { + Self { + lot: Lot::generate(0, 32.0, 1.0, rng), + } + } + + pub fn get_at(&self, pos: Vec2) -> Option<&Building> { + self.lot.get_at(pos) + } +} + +#[derive(Clone, Debug)] +pub struct Building { + pub seed: u32, +} + +#[derive(Clone, Debug)] +enum Lot { + None, + One(Building), + Many { + split_x: bool, + lots: Vec, + }, +} + +impl Lot { + pub fn generate(deep: usize, depth: f32, aspect: f32, rng: &mut impl Rng) -> Self { + let depth = if deep < 3 { + 8.0 + } else { + depth + }; + + if (depth < 1.0 || deep > 6) && !(deep < 3 || deep % 2 == 1) { + if rng.gen::() < 0.5 { + Lot::One(Building { + seed: rng.gen(), + }) + } else { + Lot::None + } + } else { + Lot::Many { + split_x: aspect > 1.0, + lots: { + let pow2 = 1 + rng.gen::() % 1; + let n = 1 << pow2; + + let new_aspect = if aspect > 1.0 { + aspect / n as f32 + } else { + aspect * n as f32 + }; + + let vari = (rng.gen::() - 0.35) * 2.8; + let new_depth = depth * 0.5 * (1.0 + vari); + + (0..n) + .map(|_| Lot::generate(deep + 1, new_depth, new_aspect, rng)) + .collect() + }, + } + } + } + + pub fn get_at(&self, pos: Vec2) -> Option<&Building> { + match self { + Lot::None => None, + Lot::One(building) => if pos.map(|e| e > 0.1 && e < 0.9).reduce_and() { + Some(building) + } else { + None + }, + Lot::Many { split_x, lots } => { + let split_dim = if *split_x { pos.x } else { pos.y }; + let idx = (split_dim * lots.len() as f32).floor() as usize; + lots[idx.min(lots.len() - 1)].get_at(if *split_x { + Vec2::new((pos.x * lots.len() as f32).fract(), pos.y) + } else { + Vec2::new(pos.x, (pos.y * lots.len() as f32).fract()) + }) + }, + } + } +}