Added experimental worldgen caves

This commit is contained in:
Joshua Barretto 2020-07-16 00:12:12 +01:00
parent a9126f7e25
commit 10757d0325
10 changed files with 245 additions and 76 deletions

View File

@ -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<impl Rng>) {
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::<Vec<_>>();
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>) -> &Place { self.places.get(id) }
pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { 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<i32>, dir: Vec2<i32>) -> Option<f32> {
} 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

View File

@ -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<u8>,
pub water_dist: Option<f32>,
pub path: Option<(f32, Vec2<f32>, Path, Vec2<f32>)>,
pub cave: Option<(f32, Vec2<f32>, Cave, Vec2<f32>)>,
pub chunk: &'a SimChunk,
}

View File

@ -95,3 +95,40 @@ pub fn apply_paths_to<'a>(
}
}
}
pub fn apply_caves_to<'a>(
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + 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(),
);
}
}
}
}
}

View File

@ -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| {

View File

@ -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
};

View File

@ -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<i32>) -> Option<(f32, Vec2<f32>, Path, Vec2<f32>)> {
/// 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<M: Clone + Lerp<Output=M>>(
&self,
wpos: Vec2<i32>,
get_way: impl Fn(&SimChunk) -> Option<(Way, M)>,
) -> Option<(f32, Vec2<f32>, M, Vec2<f32>)> {
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<i32>) -> Option<(f32, Vec2<f32>, Path, Vec2<f32>)> {
self.get_nearest_way(wpos, |chunk| Some(chunk.path))
}
pub fn get_nearest_cave(&self, wpos: Vec2<i32>) -> Option<(f32, Vec2<f32>, Cave, Vec2<f32>)> {
self.get_nearest_way(wpos, |chunk| Some(chunk.cave))
}
}
@ -1797,7 +1817,10 @@ pub struct SimChunk {
pub sites: Vec<Id<Site>>,
pub place: Option<Id<Place>>,
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,
}
}

View File

@ -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<f32>, /* 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<u8>) -> Rgb<u8> { col.map(|e| (e as f32 * 0.7) as u8) }
}

72
world/src/sim/way.rs Normal file
View File

@ -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<i8>,
/// 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<u8>) -> Rgb<u8> { 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),
}
}
}

View File

@ -121,7 +121,7 @@ pub fn tick_site_economy(index: &mut Index, site: Id<Site>, 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<Site>, 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;

View File

@ -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)