Innumerable minor improvements to towns, added bridges, better paths, more house variations, etc.

This commit is contained in:
Joshua Barretto 2020-04-11 16:50:40 +01:00
parent 8499143f77
commit 68c5612692
7 changed files with 218 additions and 114 deletions

View File

@ -8,6 +8,7 @@ use std::{
use hashbrown::{HashMap, HashSet};
use vek::*;
use rand::prelude::*;
use rand_chacha::ChaChaRng;
use common::{
terrain::TerrainChunkSize,
vol::RectVolSize,
@ -19,6 +20,7 @@ use common::{
use crate::{
sim::{WorldSim, SimChunk},
site::{Site as WorldSite, Settlement},
util::seed_expan,
};
const CARDINALS: [Vec2<i32>; 4] = [
@ -64,7 +66,7 @@ pub struct GenCtx<'a, R: Rng> {
impl Civs {
pub fn generate(seed: u32, sim: &mut WorldSim) -> Self {
let mut this = Self::default();
let mut rng = sim.rng.clone();
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
let mut ctx = GenCtx { sim, rng: &mut rng };
for _ in 0..INITIAL_CIV_COUNT {
@ -89,9 +91,32 @@ impl Civs {
// Place sites in world
for site in this.sites.iter() {
let radius = 48i32;
let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32);
let nearby_chunks = Spiral2d::new().map(|offs| site.center + offs).take(radius.pow(2) as usize);
// Flatten ground
let flatten_radius = 12.0;
if let Some(center_alt) = ctx.sim.get_alt_approx(wpos) {
for pos in nearby_chunks.clone() {
let factor = (1.0 - (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius) * 1.3;
ctx.sim
.get_mut(pos)
// Don't disrupt chunks that are near water
.filter(|chunk| !chunk.river.near_water())
.map(|chunk| {
let diff = Lerp::lerp_precise(chunk.alt, center_alt, factor) - chunk.alt;
chunk.alt += diff;
chunk.basement += diff;
chunk.rockiness = 0.0;
});
}
}
let settlement = WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng));
for pos in Spiral2d::new().map(|offs| site.center + offs).take(32usize.pow(2)) {
for pos in nearby_chunks {
ctx.sim
.get_mut(pos)
.map(|chunk| chunk.sites.push(settlement.clone()));

View File

@ -592,7 +592,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let near_cliffs = sim_chunk.near_cliffs;
let river_gouge = 0.5;
let (in_water, alt_, water_level, warp_factor) = if let Some((
let (in_water, water_dist, alt_, water_level, warp_factor) = if let Some((
max_border_river_pos,
river_chunk,
max_border_river,
@ -603,7 +603,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
//
// If we are <= water_alt, we are in the lake; otherwise, we are flowing into
// it.
let (in_water, new_alt, new_water_alt, warp_factor) = max_border_river
let (in_water, water_dist, new_alt, new_water_alt, warp_factor) = max_border_river
.river_kind
.and_then(|river_kind| {
if let RiverKind::River { cross_section } = river_kind {
@ -623,6 +623,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Some((
true,
Some(river_dist as f32),
Lerp::lerp(
new_alt - cross_section.y.max(1.0),
new_alt - 1.0,
@ -672,6 +673,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let _river_height_factor = river_dist / (river_width * 0.5);
return Some((
true,
Some(river_dist as f32),
alt_for_river.min(lake_water_alt - 1.0 - river_gouge),
lake_water_alt - river_gouge,
0.0,
@ -680,6 +682,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Some((
river_scale_factor <= 1.0,
Some(wposf.distance(river_pos) as f32),
alt_for_river,
downhill_water_alt,
river_scale_factor as f32,
@ -711,6 +714,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|| downhill_water_alt
.max(river_chunk.water_alt)
> alt_for_river,
None,
alt_for_river,
(downhill_water_alt.max(river_chunk.water_alt)
- river_gouge),
@ -720,6 +724,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
} else {
return Some((
false,
Some(lake_dist as f32),
alt_for_river,
downhill_water_alt,
river_scale_factor as f32,
@ -738,6 +743,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
if dist == Vec2::zero() {
return Some((
true,
Some(lake_dist as f32),
alt_for_river.min(lake_water_alt - 1.0 - river_gouge),
lake_water_alt - river_gouge,
0.0,
@ -756,6 +762,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
if gouge_factor == 1.0 {
return Some((
true,
None,
alt.min(lake_water_alt - 1.0 - river_gouge),
downhill_water_alt.max(lake_water_alt)
- river_gouge,
@ -764,6 +771,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
} else {
return Some((
true,
None,
alt_for_river,
if in_bounds_ {
downhill_water_alt.max(lake_water_alt)
@ -777,15 +785,21 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
}
Some((
river_scale_factor <= 1.0,
Some(lake_dist as f32),
alt_for_river,
downhill_water_alt,
river_scale_factor as f32,
))
},
RiverKind::River { .. } => {
let (_, _, _, (_, (river_pos, _), _)) =
max_border_river_dist.unwrap();
let river_dist = wposf.distance(river_pos);
// FIXME: Make water altitude accurate.
Some((
river_scale_factor <= 1.0,
Some(river_dist as f32),
alt_for_river,
downhill_water_alt,
river_scale_factor as f32,
@ -795,19 +809,21 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
})
.unwrap_or((
false,
None,
alt_for_river,
downhill_water_alt,
river_scale_factor as f32,
))
});
(in_water, new_alt, new_water_alt, warp_factor)
(in_water, water_dist, new_alt, new_water_alt, warp_factor)
} else {
(false, alt_for_river, downhill_water_alt, 1.0)
(false, None, alt_for_river, downhill_water_alt, 1.0)
};
// NOTE: To disable warp, uncomment this line.
// let warp_factor = 0.0;
let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor);
let riverless_alt = alt + riverless_alt_delta;
let alt = alt_ + riverless_alt_delta;
let basement =
alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?;
@ -1062,6 +1078,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Some(ColumnSample {
alt,
riverless_alt,
basement,
chaos,
water_level,
@ -1096,6 +1113,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
humidity,
spawn_rate,
stone_col,
water_dist,
chunk: sim_chunk,
})
@ -1105,6 +1123,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
#[derive(Clone)]
pub struct ColumnSample<'a> {
pub alt: f32,
pub riverless_alt: f32,
pub basement: f32,
pub chaos: f32,
pub water_level: f32,
@ -1127,6 +1146,7 @@ pub struct ColumnSample<'a> {
pub humidity: f32,
pub spawn_rate: f32,
pub stone_col: Rgb<u8>,
pub water_dist: Option<f32>,
pub chunk: &'a SimChunk,
}

View File

@ -100,9 +100,10 @@ impl World {
let mut sampler = self.sample_blocks();
let chunk_wpos2d = Vec2::from(chunk_pos) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
let grid_border = 4;
let zcache_grid =
Grid::populate_from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32), |offs| {
sampler.get_z_cache(chunk_wpos2d + offs)
Grid::populate_from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2, |offs| {
sampler.get_z_cache(chunk_wpos2d - grid_border + offs)
});
let mut chunk = TerrainChunk::new(base_z, stone, air, meta);
@ -115,7 +116,7 @@ impl World {
let offs = Vec2::new(x, y);
let wpos2d = chunk_wpos2d + offs;
let z_cache = match zcache_grid.get(offs) {
let z_cache = match zcache_grid.get(offs + grid_border) {
Some(Some(z_cache)) => z_cache,
_ => continue,
};
@ -146,7 +147,7 @@ impl World {
chunk_wpos2d,
|offs| {
zcache_grid
.get(offs)
.get(grid_border + offs)
.map(Option::as_ref)
.flatten()
.map(|zc| &zc.sample)

View File

@ -217,6 +217,10 @@ impl RiverData {
}
pub fn near_river(&self) -> bool { self.is_river() || self.neighbor_rivers.len() > 0 }
pub fn near_water(&self) -> bool {
self.near_river() || self.is_lake() || self.is_ocean()
}
}
/// Draw rivers and assign them heights, widths, and velocities. Take some

View File

@ -1567,8 +1567,8 @@ impl WorldSim {
}
}
pub fn get_alt_approx(&self, pos: Vec2<i32>) -> Option<f32> {
self.get_interpolated(pos, |chunk| chunk.alt)
pub fn get_alt_approx(&self, wpos: Vec2<i32>) -> Option<f32> {
self.get_interpolated(wpos, |chunk| chunk.alt)
}
pub fn get_wpos(&self, wpos: Vec2<i32>) -> Option<&SimChunk> {

View File

@ -13,6 +13,9 @@ use super::{
pub struct House {
roof_color: Rgb<u8>,
noise: RandomField,
roof_ribbing: bool,
central_supports: bool,
chimney: Option<i32>,
}
impl Archetype for House {
@ -26,6 +29,9 @@ impl Archetype for House {
rng.gen_range(50, 200),
),
noise: RandomField::new(rng.gen()),
roof_ribbing: rng.gen(),
central_supports: rng.gen(),
chimney: if rng.gen() { Some(rng.gen_range(1, 6)) } else { None },
}
}
@ -56,15 +62,20 @@ impl Archetype for House {
let foundation_height = 0 - (dist - width - 1).max(0);
let roof_height = 8 + width;
if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y > foundation_height + 1 { // Chimney shaft
return empty;
}
if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < roof_height + 2 { // Chimney
if center_offset.product() == 0 && profile.y > foundation_height + 1 && profile.y <= foundation_height + 3 { // Fireplace
if let Some(chimney_height) = self.chimney {
// Chimney shaft
if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y > foundation_height + 1 {
return empty;
} else {
return foundation;
}
// Chimney
if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < roof_height + chimney_height {
// Fireplace
if center_offset.product() == 0 && profile.y > foundation_height + 1 && profile.y <= foundation_height + 3 {
return empty;
} else {
return foundation;
}
}
}
@ -89,7 +100,8 @@ impl Archetype for House {
&& profile.y >= ceil_height
&& dist <= width + 2
{
if profile.x == 0 || dist == width + 2 || (roof_height - profile.y) % 3 == 0 { // Eaves
let is_ribbing = (roof_height - profile.y) % 3 == 0 && self.roof_ribbing;
if profile.x == 0 || dist == width + 2 || is_ribbing { // Eaves
return log;
} else {
return roof;
@ -125,7 +137,11 @@ impl Archetype for House {
}
// Wall
return if bound_offset.x == bound_offset.y || profile.x == 0 || profile.y == ceil_height { // Support beams
return if
bound_offset.x == bound_offset.y ||
(profile.x == 0 && self.central_supports) ||
profile.y == ceil_height
{ // Support beams
log
} else {
wall

View File

@ -11,8 +11,8 @@ use common::{
astar::Astar,
path::Path,
spiral::Spiral2d,
terrain::{Block, BlockKind},
vol::{BaseVol, RectSizedVol, WriteVol, Vox},
terrain::{Block, BlockKind, TerrainChunkSize},
vol::{BaseVol, RectSizedVol, RectVolSize, WriteVol, Vox},
store::{Id, Store},
};
use hashbrown::{HashMap, HashSet};
@ -71,9 +71,18 @@ pub fn center_of(p: [Vec2<f32>; 3]) -> Vec2<f32> {
Vec2::new(x, y)
}
impl SimChunk {
fn can_host_settlement(&self) -> bool {
!self.near_cliffs && !self.river.is_river() && !self.river.is_lake()
impl WorldSim {
fn can_host_settlement(&self, pos: Vec2<i32>) -> bool {
self
.get(pos)
.map(|chunk| {
chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake()
})
.unwrap_or(false)
&& self
.get_gradient_approx(pos)
.map(|grad| grad < 0.75)
.unwrap_or(false)
}
}
@ -103,6 +112,7 @@ pub struct Settlement {
farms: Store<Farm>,
structures: Vec<Structure>,
town: Option<Town>,
noise: RandomField,
}
pub struct Town {
@ -127,6 +137,7 @@ impl Settlement {
farms: Store::default(),
structures: Vec::new(),
town: None,
noise: RandomField::new(ctx.rng.gen()),
};
if let Some(sim) = ctx.sim {
@ -138,6 +149,7 @@ impl Settlement {
this.place_farms(&mut ctx);
this.place_town(&mut ctx);
this.place_paths(ctx.rng);
this.place_buildings(&mut ctx);
this
}
@ -155,10 +167,12 @@ impl Settlement {
.map(|x| (0..4).map(move |y| Vec2::new(x, y)))
.flatten()
.any(|offs| {
sim.get_wpos(wpos + offs * AREA_SIZE as i32 / 2)
.map(|chunk| !chunk.can_host_settlement())
.unwrap_or(true)
let wpos = wpos + offs * AREA_SIZE as i32 / 2;
let cpos = wpos.map(|e| e.div_euclid(TerrainChunkSize::RECT_SIZE.x as i32));
sim
.can_host_settlement(cpos)
})
|| rng.gen_range(0, 16) == 0 // Randomly consider some tiles inaccessible
{
self.land.set(tile, hazard);
}
@ -252,50 +266,7 @@ impl Settlement {
.plot_at_mut(base_tile)
.map(|plot| *plot = Plot::Town);
for _ in 0..ctx.rng.gen_range(10, 30) {
for _ in 0..10 {
let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2)
+ Vec2::<i32>::zero().map(|_| ctx.rng.gen_range(-(AREA_SIZE as i32) * 3, AREA_SIZE as i32 * 3));
if let Some(Plot::Town) = self.land
.plot_at(house_pos.map(|e| e.div_euclid(AREA_SIZE as i32)))
{} else {
continue;
}
let structure = Structure {
kind: StructureKind::House(HouseBuilding::generate(ctx.rng, Vec3::new(
house_pos.x,
house_pos.y,
ctx.sim
.and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
.unwrap_or(0.0)
.ceil() as i32,
))),
};
let bounds = structure.bounds_2d();
// Check for collision with other structures
if self.structures
.iter()
.any(|s| s.bounds_2d().collides_with_aabr(bounds))
{
continue;
}
self.structures.push(structure);
break;
}
}
if i == 0 {
/*
for dir in CARDINALS.iter() {
self.land.set(base_tile + *dir, town);
}
*/
self.town = Some(Town { base_tile });
origin = base_tile;
}
@ -307,6 +278,7 @@ impl Settlement {
.iter()
.filter_map(|dir| {
self.land.find_tile_dir(origin, *dir, |plot| match plot {
Some(Plot::Hazard) => false,
Some(Plot::Town) => false,
_ => true,
})
@ -318,8 +290,8 @@ impl Settlement {
.find_path(spokes[i], spokes[(i + 1) % spokes.len()], |_, to| match to
.map(|to| self.land.plot(to.plot))
{
Some(Plot::Hazard) => 10000.0,
Some(Plot::Town) => 1000.0,
Some(Plot::Hazard) => 100000.0,
Some(Plot::Town) => 10000.0,
_ => 1.0,
})
.map(|path| wall_path.extend(path.iter().copied()));
@ -346,6 +318,54 @@ impl Settlement {
.write_path(&wall_path, WayKind::Wall, buildable, true);
}
pub fn place_buildings(&mut self, ctx: &mut GenCtx<impl Rng>) {
let town_center = if let Some(town) = self.town.as_ref() {
town.base_tile
} else {
return;
};
for tile in Spiral2d::new().map(|offs| town_center + offs).take(16usize.pow(2)) {
for _ in 0..ctx.rng.gen_range(1, 5) {
for _ in 0..10 {
let house_pos = tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2)
+ Vec2::<i32>::zero().map(|_| ctx.rng.gen_range(-(AREA_SIZE as i32) / 2, AREA_SIZE as i32) / 2);
let tile_pos = house_pos.map(|e| e.div_euclid(AREA_SIZE as i32));
if !matches!(self.land.plot_at(tile_pos), Some(Plot::Town))
|| self.land.tile_at(tile_pos).map(|t| t.contains(WayKind::Path)).unwrap_or(true)
{
continue;
}
let structure = Structure {
kind: StructureKind::House(HouseBuilding::generate(ctx.rng, Vec3::new(
house_pos.x,
house_pos.y,
ctx.sim
.and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
.unwrap_or(0.0)
.ceil() as i32,
))),
};
let bounds = structure.bounds_2d();
// Check for collision with other structures
if self.structures
.iter()
.any(|s| s.bounds_2d().collides_with_aabr(bounds))
{
continue;
}
self.structures.push(structure);
break;
}
}
}
}
pub fn place_farms(&mut self, ctx: &mut GenCtx<impl Rng>) {
const FARM_COUNT: usize = 6;
const FIELDS_PER_FARM: usize = 5;
@ -416,7 +436,11 @@ impl Settlement {
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
SpawnRules {
trees: self.land.get_at_block(wpos - self.origin).plot.is_none(),
trees: self.land
.get_at_block(wpos - self.origin)
.plot
.map(|p| if let Plot::Hazard = p { true } else { false })
.unwrap_or(true),
}
}
@ -441,23 +465,58 @@ impl Settlement {
} else {
continue;
};
let surface_z = col_sample.alt.floor() as i32;
let surface_z = col_sample.riverless_alt.floor() as i32;
// Sample settlement
let sample = self.land.get_at_block(rpos);
// Ground color
if let Some(color) = self.get_color(rpos) {
let noisy_color = |col: Rgb<u8>, factor: u32| {
let nz = self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, surface_z));
col.map(|e| (e as u32 + nz % (factor * 2)).saturating_sub(factor).min(255) as u8)
};
// Paths
if let Some((WayKind::Path, dist, nearest)) = sample.way {
let inset = -1;
// Try to use the column at the centre of the path for sampling to make them flatter
let col = get_column(offs + (nearest.floor().map(|e| e as i32) - rpos)).unwrap_or(col_sample);
let bridge_offset = if let Some(water_dist) = col.water_dist {
((water_dist.abs() * 0.15).min(f32::consts::PI).cos() + 1.0) * 5.0
} else {
0.0
};
let surface_z = (col.riverless_alt + bridge_offset).floor() as i32;
for z in inset - 2..inset {
vol.set(
Vec3::new(offs.x, offs.y, surface_z + z),
if bridge_offset >= 2.0 {
Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8))
} else {
Block::new(BlockKind::Normal, noisy_color(Rgb::new(90, 70, 50), 8))
},
);
}
let head_space = 6;//(6 - (dist * 0.4).powf(6.0).round() as i32).max(1);
for z in inset..inset + head_space {
vol.set(
Vec3::new(offs.x, offs.y, surface_z + z),
Block::empty(),
);
}
// Ground colour
} else if let Some(color) = self.get_color(rpos) {
for z in -3..5 {
vol.set(
Vec3::new(offs.x, offs.y, surface_z + z),
if z >= 0 { Block::empty() } else { Block::new(BlockKind::Normal, color) },
if z >= 0 { Block::empty() } else { Block::new(BlockKind::Normal, noisy_color(color, 4)) },
);
}
}
// Walls
if let Some((WayKind::Wall, dist)) = sample.way {
if let Some((WayKind::Wall, dist, _)) = sample.way {
let color = Lerp::lerp(
Rgb::new(130i32, 100, 0),
Rgb::new(90, 70, 50),
@ -485,24 +544,6 @@ impl Settlement {
);
}
}
// Paths
if let Some((WayKind::Path, dist)) = sample.way {
let inset = -1;
for z in -3..inset {
vol.set(
Vec3::new(offs.x, offs.y, surface_z + z),
Block::new(BlockKind::Normal, Rgb::new(90, 70, 50)),
);
}
let head_space = (6 - (dist * 0.4).powf(6.0).round() as i32).max(1);
for z in inset..inset + head_space {
vol.set(
Vec3::new(offs.x, offs.y, surface_z + z),
Block::empty(),
);
}
}
}
}
@ -550,9 +591,9 @@ impl Settlement {
}
match sample.way {
Some((WayKind::Path, _)) => return Some(Rgb::new(90, 70, 50)),
Some((WayKind::Hedge, _)) => return Some(Rgb::new(0, 150, 0)),
Some((WayKind::Wall, _)) => return Some(Rgb::new(60, 60, 60)),
Some((WayKind::Path, _, _)) => return Some(Rgb::new(90, 70, 50)),
Some((WayKind::Hedge, _, _)) => return Some(Rgb::new(0, 150, 0)),
Some((WayKind::Wall, _, _)) => return Some(Rgb::new(60, 60, 60)),
_ => {},
}
@ -560,11 +601,7 @@ impl Settlement {
Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)),
Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)),
Some(Plot::Water) => return Some(Rgb::new(100, 150, 250)),
Some(Plot::Town) => return Some(if pos.map(|e| e.rem_euclid(4) < 2).reduce(|x, y| x ^ y) {
Rgb::new(200, 130, 120)
} else {
Rgb::new(160, 150, 120)
}),
Some(Plot::Town) => return Some(Rgb::new(130, 120, 80)),
Some(Plot::Field { seed, .. }) => {
let furrow_dirs = [
Vec2::new(1, 0),
@ -651,7 +688,7 @@ impl Tile {
#[derive(Default)]
pub struct Sample<'a> {
plot: Option<&'a Plot>,
way: Option<(&'a WayKind, f32)>,
way: Option<(&'a WayKind, f32, Vec2<f32>)>,
tower: Option<(&'a Tower, Vec2<i32>)>,
}
@ -690,14 +727,15 @@ impl Land {
for (i, dir) in CARDINALS.iter().enumerate() {
let map = [1, 5, 7, 3];
let line = [
neighbors[4].0.map(|e| e as f32),
neighbors[map[i]].0.map(|e| e as f32),
];
let line = LineSegment2 {
start: neighbors[4].0.map(|e| e as f32),
end: neighbors[map[i]].0.map(|e| e as f32),
};
if let Some(way) = center_tile.and_then(|tile| tile.ways[i].as_ref()) {
let dist = dist_to_line(line, pos.map(|e| e as f32));
let proj_point = line.projected_point(pos.map(|e| e as f32));
let dist = proj_point.distance(pos.map(|e| e as f32));
if dist < way.width() {
sample.way = Some((way, dist));
sample.way = Some((way, dist, proj_point));
}
}
}