From 4b94d43abdc5fab3cbe85db55822686505951b02 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 16 Jul 2020 00:12:12 +0100 Subject: [PATCH] Added experimental worldgen caves --- world/src/civ/mod.rs | 74 +++++++++++++++++++++++++++++++++++---- world/src/column/mod.rs | 6 +++- world/src/layer/mod.rs | 37 ++++++++++++++++++++ world/src/lib.rs | 3 +- world/src/sim/map.rs | 7 +++- world/src/sim/mod.rs | 64 ++++++++++++++++++++++----------- world/src/sim/path.rs | 41 ---------------------- world/src/sim/way.rs | 72 +++++++++++++++++++++++++++++++++++++ world/src/sim2/mod.rs | 5 +-- world/src/site/economy.rs | 12 +++++-- 10 files changed, 245 insertions(+), 76 deletions(-) delete mode 100644 world/src/sim/path.rs create mode 100644 world/src/sim/way.rs diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 9a34d83de0..96ff4c98f9 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -76,6 +76,10 @@ impl Civs { let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); let mut ctx = GenCtx { sim, rng }; + for _ in 0..100 { + this.generate_cave(&mut ctx); + } + for _ in 0..INITIAL_CIV_COUNT { debug!("Creating civilisation..."); if this.birth_civ(&mut ctx.reseed()).is_none() { @@ -208,6 +212,62 @@ impl Civs { this } + // TODO: Move this + fn generate_cave(&self, ctx: &mut GenCtx) { + let mut pos = ctx.sim + .get_size() + .map(|sz| ctx.rng.gen_range(0, sz as i32) as f32); + let mut vel = pos + .map2(ctx.sim.get_size(), |pos, sz| sz as f32 / 2.0 - pos) + .try_normalized() + .unwrap_or_else(Vec2::unit_y); + + let path = (-100..100) + .filter_map(|i: i32| { + let depth = (i.abs() as f32 / 100.0 * std::f32::consts::PI / 2.0).cos(); + vel = (vel + Vec2::new( + ctx.rng.gen_range(-0.25, 0.25), + ctx.rng.gen_range(-0.25, 0.25), + )) + .try_normalized() + .unwrap_or_else(Vec2::unit_y); + let old_pos = pos.map(|e| e as i32); + pos = (pos + vel * 0.5).clamped(Vec2::zero(), ctx.sim.get_size().map(|e| e as f32 - 1.0)); + Some((pos.map(|e| e as i32), depth)).filter(|(pos, _)| *pos != old_pos) + }) + .collect::>(); + + for locs in path.windows(3) { + let to_prev_idx = NEIGHBORS + .iter() + .enumerate() + .find(|(_, dir)| **dir == locs[0].0 - locs[1].0) + .expect("Track locations must be neighbors") + .0; + let to_next_idx = NEIGHBORS + .iter() + .enumerate() + .find(|(_, dir)| **dir == locs[2].0 - locs[1].0) + .expect("Track locations must be neighbors") + .0; + + ctx.sim.get_mut(locs[0].0).unwrap().cave.0.neighbors |= + 1 << ((to_prev_idx as u8 + 4) % 8); + ctx.sim.get_mut(locs[2].0).unwrap().cave.0.neighbors |= + 1 << ((to_next_idx as u8 + 4) % 8); + let mut chunk = ctx.sim.get_mut(locs[1].0).unwrap(); + chunk.cave.0.neighbors |= + (1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8)); + let depth = locs[1].1 * 250.0; + chunk.cave.1.alt = chunk.alt - depth + ctx.rng.gen_range(-4.0, 4.0) * (depth > 10.0) as i32 as f32; + chunk.cave.1.width = ctx.rng.gen_range(12.0, 32.0); + chunk.cave.0.offset = Vec2::new( + ctx.rng.gen_range(-16, 17), + ctx.rng.gen_range(-16, 17), + ); + } + } + pub fn place(&self, id: Id) -> &Place { self.places.get(id) } pub fn sites(&self) -> impl Iterator + '_ { self.sites.values() } @@ -425,16 +485,16 @@ impl Civs { .expect("Track locations must be neighbors") .0; - ctx.sim.get_mut(locs[0]).unwrap().path.neighbors |= + ctx.sim.get_mut(locs[0]).unwrap().path.0.neighbors |= 1 << ((to_prev_idx as u8 + 4) % 8); - ctx.sim.get_mut(locs[2]).unwrap().path.neighbors |= + ctx.sim.get_mut(locs[2]).unwrap().path.0.neighbors |= 1 << ((to_next_idx as u8 + 4) % 8); let mut chunk = ctx.sim.get_mut(locs[1]).unwrap(); - chunk.path.neighbors |= + chunk.path.0.neighbors |= (1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8)); - chunk.path.offset = Vec2::new( - ctx.rng.gen_range(-16.0, 16.0), - ctx.rng.gen_range(-16.0, 16.0), + chunk.path.0.offset = Vec2::new( + ctx.rng.gen_range(-16, 17), + ctx.rng.gen_range(-16, 17), ); } @@ -570,7 +630,7 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2, dir: Vec2) -> Option { } else { 0.0 }; - let wild_cost = if b_chunk.path.is_path() { + let wild_cost = if b_chunk.path.0.is_way() { 0.0 // Traversing existing paths has no additional cost! } else { 2.0 diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 129b37545c..7a1cdc72af 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -2,7 +2,8 @@ use crate::{ all::ForestKind, block::StructureMeta, sim::{ - local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, Path, RiverKind, SimChunk, WorldSim, + local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, + Path, Cave, RiverKind, SimChunk, WorldSim, }, util::Sampler, Index, CONFIG, @@ -1082,6 +1083,7 @@ where }; let path = sim.get_nearest_path(wpos); + let cave = sim.get_nearest_cave(wpos); Some(ColumnSample { alt, @@ -1131,6 +1133,7 @@ where stone_col, water_dist, path, + cave, chunk: sim_chunk, }) @@ -1165,6 +1168,7 @@ pub struct ColumnSample<'a> { pub stone_col: Rgb, pub water_dist: Option, pub path: Option<(f32, Vec2, Path, Vec2)>, + pub cave: Option<(f32, Vec2, Cave, Vec2)>, pub chunk: &'a SimChunk, } diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 7cb81635e6..565a89b9b5 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -95,3 +95,40 @@ pub fn apply_paths_to<'a>( } } } + +pub fn apply_caves_to<'a>( + wpos2d: Vec2, + mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), +) { + for y in 0..vol.size_xy().y as i32 { + for x in 0..vol.size_xy().x as i32 { + let offs = Vec2::new(x, y); + + let wpos2d = wpos2d + offs; + + // Sample terrain + let col_sample = if let Some(col_sample) = get_column(offs) { + col_sample + } else { + continue; + }; + let surface_z = col_sample.riverless_alt.floor() as i32; + + if let Some((cave_dist, cave_nearest, cave, _)) = col_sample + .cave + .filter(|(dist, _, cave, _)| *dist < cave.width) + { + let cave_x = (cave_dist / cave.width).min(1.0); + let height = (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width; + + for z in (cave.alt - height) as i32..(cave.alt + height) as i32 { + let _ = vol.set( + Vec3::new(offs.x, offs.y, z), + Block::empty(), + ); + } + } + } + } +} diff --git a/world/src/lib.rs b/world/src/lib.rs index 9aabf4dff8..96d8865da2 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -175,8 +175,9 @@ impl World { let mut rng = rand::thread_rng(); - // Apply paths + // Apply layers (paths, caves, etc.) layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk); + layer::apply_caves_to(chunk_wpos2d, sample_get, &mut chunk); // Apply site generation sim_chunk.sites.iter().for_each(|site| { diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index ebed7fae80..6655b84e35 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -157,6 +157,7 @@ impl MapConfig { downhill, river_kind, is_path, + is_cave, near_site, ) = sampler .get(pos) @@ -169,7 +170,8 @@ impl MapConfig { sample.temp, sample.downhill, sample.river.river_kind, - sample.path.is_path(), + sample.path.0.is_way(), + sample.cave.0.is_way(), sample.sites.iter().any(|site| { index.sites[*site] .get_origin() @@ -188,6 +190,7 @@ impl MapConfig { None, false, false, + false, )); let humidity = humidity.min(1.0).max(0.0); let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5; @@ -317,6 +320,8 @@ impl MapConfig { (0x57, 0x39, 0x33, 0xFF) } else if is_path { (0x37, 0x29, 0x23, 0xFF) + } else if is_cave { + (0x37, 0x37, 0x37, 0xFF) } else { rgba }; diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index d90f07d178..584c95dbc0 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -2,7 +2,7 @@ mod diffusion; mod erosion; mod location; mod map; -mod path; +mod way; mod util; // Reexports @@ -15,7 +15,7 @@ pub use self::{ }, location::Location, map::{MapConfig, MapDebug}, - path::{Path, PathData}, + way::{Way, Path, Cave}, util::{ cdf_irwin_hall, downhill, get_oceans, local_cells, map_edge_factor, neighbors, uniform_idx_as_vec2, uniform_noise, uphill, vec2_as_uniform_idx, InverseCdf, ScaleBias, @@ -1700,10 +1700,14 @@ impl WorldSim { Some(z0 + z1 + z2 + z3) } - /// Return the distance to the nearest path in blocks, along with the - /// closest point on the path, the path metadata, and the tangent vector - /// of that path. - pub fn get_nearest_path(&self, wpos: Vec2) -> Option<(f32, Vec2, Path, Vec2)> { + /// Return the distance to the nearest way in blocks, along with the + /// closest point on the way, the way metadata, and the tangent vector + /// of that way. + pub fn get_nearest_way>( + &self, + wpos: Vec2, + get_way: impl Fn(&SimChunk) -> Option<(Way, M)>, + ) -> Option<(f32, Vec2, M, Vec2)> { let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { e.div_euclid(sz as i32) }); @@ -1713,32 +1717,34 @@ impl WorldSim { }) }; + let get_way = &get_way; LOCALITY .iter() .filter_map(|ctrl| { - let chunk = self.get(chunk_pos + *ctrl)?; - let ctrl_pos = - get_chunk_centre(chunk_pos + *ctrl).map(|e| e as f32) + chunk.path.offset; + let (way, meta) = get_way(self.get(chunk_pos + *ctrl)?)?; + let ctrl_pos = get_chunk_centre(chunk_pos + *ctrl).map(|e| e as f32) + way.offset.map(|e| e as f32); - let chunk_connections = chunk.path.neighbors.count_ones(); + let chunk_connections = way.neighbors.count_ones(); if chunk_connections == 0 { return None; } - let (start_pos, _start_idx) = if chunk_connections != 2 { - (ctrl_pos, None) + let (start_pos, _start_idx, start_meta) = if chunk_connections != 2 { + (ctrl_pos, None, meta.clone()) } else { let (start_idx, start_rpos) = NEIGHBORS .iter() .copied() .enumerate() - .find(|(i, _)| chunk.path.neighbors & (1 << *i as u8) != 0) + .find(|(i, _)| way.neighbors & (1 << *i as u8) != 0) .unwrap(); let start_pos_chunk = chunk_pos + *ctrl + start_rpos; + let (start_way, start_meta) = get_way(self.get(start_pos_chunk)?)?; ( get_chunk_centre(start_pos_chunk).map(|e| e as f32) - + self.get(start_pos_chunk)?.path.offset, + + start_way.offset.map(|e| e as f32), Some(start_idx), + start_meta, ) }; @@ -1746,11 +1752,12 @@ impl WorldSim { NEIGHBORS .iter() .enumerate() - .filter(move |(i, _)| chunk.path.neighbors & (1 << *i as u8) != 0) + .filter(move |(i, _)| way.neighbors & (1 << *i as u8) != 0) .filter_map(move |(_, end_rpos)| { let end_pos_chunk = chunk_pos + *ctrl + end_rpos; + let (end_way, end_meta) = get_way(self.get(end_pos_chunk)?)?; let end_pos = get_chunk_centre(end_pos_chunk).map(|e| e as f32) - + self.get(end_pos_chunk)?.path.offset; + + end_way.offset.map(|e| e as f32); let bez = QuadraticBezier2 { start: (start_pos + ctrl_pos) / 2.0, @@ -1763,7 +1770,12 @@ impl WorldSim { .clamped(0.0, 1.0); let pos = bez.evaluate(nearest_interval); let dist_sqrd = pos.distance_squared(wpos.map(|e| e as f32)); - Some((dist_sqrd, pos, chunk.path.path, move || { + let meta = if nearest_interval < 0.5 { + Lerp::lerp(start_meta.clone(), meta.clone(), 0.5 + nearest_interval) + } else { + Lerp::lerp(meta.clone(), end_meta, nearest_interval - 0.5) + }; + Some((dist_sqrd, pos, meta, move || { bez.evaluate_derivative(nearest_interval).normalized() })) }), @@ -1771,7 +1783,15 @@ impl WorldSim { }) .flatten() .min_by_key(|(dist_sqrd, _, _, _)| (dist_sqrd * 1024.0) as i32) - .map(|(dist, pos, path, calc_tangent)| (dist.sqrt(), pos, path, calc_tangent())) + .map(|(dist, pos, meta, calc_tangent)| (dist.sqrt(), pos, meta, calc_tangent())) + } + + pub fn get_nearest_path(&self, wpos: Vec2) -> Option<(f32, Vec2, Path, Vec2)> { + self.get_nearest_way(wpos, |chunk| Some(chunk.path)) + } + + pub fn get_nearest_cave(&self, wpos: Vec2) -> Option<(f32, Vec2, Cave, Vec2)> { + self.get_nearest_way(wpos, |chunk| Some(chunk.cave)) } } @@ -1797,7 +1817,10 @@ pub struct SimChunk { pub sites: Vec>, pub place: Option>, - pub path: PathData, + + pub path: (Way, Path), + pub cave: (Way, Cave), + pub contains_waypoint: bool, } @@ -2033,7 +2056,8 @@ impl SimChunk { sites: Vec::new(), place: None, - path: PathData::default(), + path: Default::default(), + cave: Default::default(), contains_waypoint: false, } } diff --git a/world/src/sim/path.rs b/world/src/sim/path.rs deleted file mode 100644 index 3756b14dc6..0000000000 --- a/world/src/sim/path.rs +++ /dev/null @@ -1,41 +0,0 @@ -use vek::*; - -#[derive(Copy, Clone, Debug)] -pub struct Path { - pub width: f32, // Actually radius -} - -#[derive(Debug)] -pub struct PathData { - pub offset: Vec2, /* Offset from centre of chunk: must not be more than half chunk - * width in any direction */ - pub path: Path, - pub neighbors: u8, // One bit for each neighbor -} - -impl PathData { - pub fn is_path(&self) -> bool { self.neighbors != 0 } - - pub fn clear(&mut self) { self.neighbors = 0; } -} - -impl Default for PathData { - fn default() -> Self { - Self { - offset: Vec2::zero(), - path: Path { width: 5.0 }, - neighbors: 0, - } - } -} - -impl Path { - /// Return the number of blocks of headspace required at the given path - /// distance - pub fn head_space(&self, dist: f32) -> i32 { - (8 - (dist * 0.25).powf(6.0).round() as i32).max(1) - } - - /// Get the surface colour of a path given the surrounding surface color - pub fn surface_color(&self, col: Rgb) -> Rgb { col.map(|e| (e as f32 * 0.7) as u8) } -} diff --git a/world/src/sim/way.rs b/world/src/sim/way.rs new file mode 100644 index 0000000000..bc573a4b17 --- /dev/null +++ b/world/src/sim/way.rs @@ -0,0 +1,72 @@ +use vek::*; + +#[derive(Copy, Clone, Debug, Default)] +pub struct Way { + /// Offset from chunk center in blocks (no more than half chunk width) + pub offset: Vec2, + /// Neighbor connections, one bit each + pub neighbors: u8, +} + +impl Way { + pub fn is_way(&self) -> bool { self.neighbors != 0 } + + pub fn clear(&mut self) { self.neighbors = 0; } +} + +#[derive(Copy, Clone, Debug)] +pub struct Path { + pub width: f32, // Actually radius +} + +impl Default for Path { + fn default() -> Self { + Self { width: 5.0 } + } +} + +impl Lerp for Path { + type Output = Self; + + fn lerp_unclamped(from: Self, to: Self, factor: f32) -> Self::Output { + Self { width: Lerp::lerp(from.width, to.width, factor) } + } +} + +impl Path { + /// Return the number of blocks of headspace required at the given path + /// distance + /// TODO: make this generic over width + pub fn head_space(&self, dist: f32) -> i32 { + (8 - (dist * 0.25).powf(6.0).round() as i32).max(1) + } + + /// Get the surface colour of a path given the surrounding surface color + pub fn surface_color(&self, col: Rgb) -> Rgb { col.map(|e| (e as f32 * 0.7) as u8) } +} + +#[derive(Copy, Clone, Debug)] +pub struct Cave { + pub width: f32, // Actually radius + pub alt: f32, // Actually radius +} + +impl Default for Cave { + fn default() -> Self { + Self { + width: 32.0, + alt: 0.0, + } + } +} + +impl Lerp for Cave { + type Output = Self; + + fn lerp_unclamped(from: Self, to: Self, factor: f32) -> Self::Output { + Self { + width: Lerp::lerp(from.width, to.width, factor), + alt: Lerp::lerp(from.alt, to.alt, factor), + } + } +} diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index 1a45d48511..3d07e8ad34 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -121,7 +121,7 @@ pub fn tick_site_economy(index: &mut Index, site: Id, dt: f32) { } } - let mut supply = site.economy.stocks.clone();//MapVec::from_default(0.0); + let mut supply = site.economy.stocks.clone(); //MapVec::from_default(0.0); for (labor, (output_good, _)) in productivity.iter() { supply[*output_good] += site.economy.yields[labor] * site.economy.labors[labor] * site.economy.pop; @@ -231,7 +231,8 @@ pub fn tick_site_economy(index: &mut Index, site: Id, dt: f32) { let (stock, rate) = productivity[*labor]; let workers = site.economy.labors[*labor] * site.economy.pop; let final_rate = rate; - let yield_per_worker = labor_productivity * final_rate * (1.0 + workers / 100.0).min(3.0); + let yield_per_worker = + labor_productivity * final_rate * (1.0 + workers / 100.0).min(3.0); site.economy.yields[*labor] = yield_per_worker; site.economy.productivity[*labor] = labor_productivity; let total_output = yield_per_worker * workers; diff --git a/world/src/site/economy.rs b/world/src/site/economy.rs index 2c59d5c31b..f152560702 100644 --- a/world/src/site/economy.rs +++ b/world/src/site/economy.rs @@ -102,9 +102,15 @@ impl Economy { pub fn replenish(&mut self, time: f32) { use rand::Rng; - for (i, (g, v)) in [(Wheat, 50.0), (Logs, 20.0), (Rock, 120.0), (Game, 12.0), (Fish, 10.0)] - .iter() - .enumerate() + for (i, (g, v)) in [ + (Wheat, 50.0), + (Logs, 20.0), + (Rock, 120.0), + (Game, 12.0), + (Fish, 10.0), + ] + .iter() + .enumerate() { self.stocks[*g] = (*v * (1.25 + (((time * 0.0001 + i as f32).sin() + 1.0) % 1.0) * 0.5)