Improvements to town generation

This commit is contained in:
Joshua Barretto 2020-04-11 20:04:19 +01:00
parent 68c5612692
commit d3bf856c33
4 changed files with 94 additions and 50 deletions

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, water_dist, alt_, water_level, warp_factor) = if let Some((
let (in_water, water_dist, alt_, water_level, riverless_alt, 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, water_dist, new_alt, new_water_alt, warp_factor) = max_border_river
let (in_water, water_dist, new_alt, new_water_alt, riverless_alt, warp_factor) = max_border_river
.river_kind
.and_then(|river_kind| {
if let RiverKind::River { cross_section } = river_kind {
@ -621,15 +621,18 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let river_dist = wposf.distance(river_pos);
let river_height_factor = river_dist / (river_width * 0.5);
let valley_alt = Lerp::lerp(
new_alt - cross_section.y.max(1.0),
new_alt - 1.0,
(river_height_factor * river_height_factor) as f32,
);
Some((
true,
Some(river_dist as f32),
Lerp::lerp(
new_alt - cross_section.y.max(1.0),
new_alt - 1.0,
(river_height_factor * river_height_factor) as f32,
),
valley_alt,
new_alt,
valley_alt + river_gouge,
0.0,
))
} else {
@ -676,6 +679,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Some(river_dist as f32),
alt_for_river.min(lake_water_alt - 1.0 - river_gouge),
lake_water_alt - river_gouge,
alt_for_river.min(lake_water_alt - 1.0),
0.0,
));
}
@ -685,6 +689,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Some(wposf.distance(river_pos) as f32),
alt_for_river,
downhill_water_alt,
alt_for_river,
river_scale_factor as f32,
))
},
@ -714,10 +719,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|| downhill_water_alt
.max(river_chunk.water_alt)
> alt_for_river,
None,
Some(lake_dist as f32),
alt_for_river,
(downhill_water_alt.max(river_chunk.water_alt)
- river_gouge),
alt_for_river,
river_scale_factor as f32
* (1.0 - gouge_factor),
));
@ -727,6 +733,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Some(lake_dist as f32),
alt_for_river,
downhill_water_alt,
alt_for_river,
river_scale_factor as f32,
));
}
@ -746,6 +753,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Some(lake_dist as f32),
alt_for_river.min(lake_water_alt - 1.0 - river_gouge),
lake_water_alt - river_gouge,
alt_for_river.min(lake_water_alt - 1.0),
0.0,
));
}
@ -766,6 +774,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
alt.min(lake_water_alt - 1.0 - river_gouge),
downhill_water_alt.max(lake_water_alt)
- river_gouge,
alt.min(lake_water_alt - 1.0),
0.0,
));
} else {
@ -775,10 +784,10 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
alt_for_river,
if in_bounds_ {
downhill_water_alt.max(lake_water_alt)
- river_gouge
} else {
downhill_water_alt - river_gouge
},
downhill_water_alt
} - river_gouge,
alt_for_river,
river_scale_factor as f32 * (1.0 - gouge_factor),
));
}
@ -788,6 +797,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Some(lake_dist as f32),
alt_for_river,
downhill_water_alt,
alt_for_river,
river_scale_factor as f32,
))
},
@ -802,6 +812,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Some(river_dist as f32),
alt_for_river,
downhill_water_alt,
alt_for_river,
river_scale_factor as f32,
))
},
@ -812,19 +823,20 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
None,
alt_for_river,
downhill_water_alt,
alt_for_river,
river_scale_factor as f32,
))
});
(in_water, water_dist, new_alt, new_water_alt, warp_factor)
(in_water, water_dist, new_alt, new_water_alt, riverless_alt, warp_factor)
} else {
(false, None, alt_for_river, downhill_water_alt, 1.0)
(false, None, alt_for_river, downhill_water_alt, alt_for_river, 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 riverless_alt = riverless_alt + riverless_alt_delta;
let basement =
alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?;

View File

@ -9,7 +9,7 @@ use crate::{
};
use common::{
terrain::Block,
vol::{BaseVol, RectSizedVol, WriteVol},
vol::{BaseVol, RectSizedVol, ReadVol, WriteVol},
};
use std::{fmt, sync::Arc};
use vek::*;
@ -40,7 +40,7 @@ impl Site {
&'a self,
wpos2d: Vec2<i32>,
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + WriteVol),
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
) {
match self {
Site::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol),

View File

@ -56,6 +56,7 @@ impl Archetype for House {
let wall = make_block(200, 180, 150);
let roof = make_block(self.roof_color.r, self.roof_color.g, self.roof_color.b);
let empty = Some(Some(Block::empty()));
let fire = Some(Some(Block::new(BlockKind::Ember, Rgb::white())));
let ceil_height = 6;
let width = -3 + branch.locus + if profile.y >= ceil_height { 1 } else { 0 };
@ -64,8 +65,12 @@ impl Archetype for House {
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;
if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y >= foundation_height + 1 {
return if profile.y == foundation_height + 1 {
fire
} else {
empty
};
}
// Chimney

View File

@ -12,7 +12,7 @@ use common::{
path::Path,
spiral::Spiral2d,
terrain::{Block, BlockKind, TerrainChunkSize},
vol::{BaseVol, RectSizedVol, RectVolSize, WriteVol, Vox},
vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox},
store::{Id, Store},
};
use hashbrown::{HashMap, HashSet};
@ -76,7 +76,7 @@ impl WorldSim {
self
.get(pos)
.map(|chunk| {
chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake()
!chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake()
})
.unwrap_or(false)
&& self
@ -157,7 +157,7 @@ impl Settlement {
/// Designate hazardous terrain based on world data
pub fn designate_from_world(&mut self, sim: &WorldSim, rng: &mut impl Rng) {
let tile_radius = self.radius() as i32 / AREA_SIZE as i32;
let hazard = self.land.new_plot(Plot::Hazard);
let hazard = self.land.hazard;
Spiral2d::new()
.take_while(|tile| tile.map(|e| e.abs()).reduce_max() < tile_radius)
.for_each(|tile| {
@ -169,8 +169,7 @@ impl Settlement {
.any(|offs| {
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)
!sim.can_host_settlement(cpos)
})
|| rng.gen_range(0, 16) == 0 // Randomly consider some tiles inaccessible
{
@ -278,7 +277,7 @@ impl Settlement {
.iter()
.filter_map(|dir| {
self.land.find_tile_dir(origin, *dir, |plot| match plot {
Some(Plot::Hazard) => false,
Some(Plot::Water) => false,
Some(Plot::Town) => false,
_ => true,
})
@ -290,9 +289,10 @@ impl Settlement {
.find_path(spokes[i], spokes[(i + 1) % spokes.len()], |_, to| match to
.map(|to| self.land.plot(to.plot))
{
Some(Plot::Hazard) => 100000.0,
Some(Plot::Town) => 10000.0,
_ => 1.0,
Some(Plot::Hazard) => 200.0,
Some(Plot::Water) => 40.0,
Some(Plot::Town) => 1000.0,
_ => 10.0,
})
.map(|path| wall_path.extend(path.iter().copied()));
}
@ -448,7 +448,7 @@ impl Settlement {
&'a self,
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + WriteVol),
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
) {
let rand_field = RandomField::new(0);
@ -482,13 +482,13 @@ impl Settlement {
// 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
((water_dist.abs() * 0.2).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 {
for z in inset - 3..inset {
vol.set(
Vec3::new(offs.x, offs.y, surface_z + z),
if bridge_offset >= 2.0 {
@ -498,7 +498,7 @@ impl Settlement {
},
);
}
let head_space = 6;//(6 - (dist * 0.4).powf(6.0).round() as i32).max(1);
let head_space = (8 - (dist * 0.25).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),
@ -507,11 +507,16 @@ impl Settlement {
}
// 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, noisy_color(color, 4)) },
);
for z in -8..6 {
let pos = Vec3::new(offs.x, offs.y, surface_z + z);
if z >= 0 {
if vol.get(pos).unwrap().kind() != BlockKind::Water {
vol.set(pos, Block::empty());
}
} else {
vol.set(pos, Block::new(BlockKind::Normal, noisy_color(color, 4)));
}
}
}
@ -523,7 +528,15 @@ impl Settlement {
(rand_field.get(wpos2d.into()) % 256) as f32 / 256.0,
)
.map(|e| (e % 256) as u8);
for z in 0..12 {
let z_offset = if let Some(water_dist) = col_sample.water_dist {
// Water gate
((water_dist.abs() * 0.45).min(f32::consts::PI).cos() + 1.0) * 4.0
} else {
0.0
} as i32;
for z in z_offset..12 {
if dist / WayKind::Wall.width()
< ((1.0 - z as f32 / 12.0) * 2.0).min(1.0)
{
@ -566,7 +579,13 @@ impl Settlement {
for x in bounds.min.x..bounds.max.x + 1 {
for y in bounds.min.y..bounds.max.y + 1 {
for z in bounds.min.z..bounds.max.z + 1 {
let col = if let Some(col) = get_column(offs) {
col
} else {
continue;
};
for z in bounds.min.z.min(col.alt as i32 - 1)..bounds.max.z + 1 {
let rpos = Vec3::new(x, y, z);
let wpos = Vec3::from(self.origin) + rpos;
let coffs = wpos - Vec3::from(wpos2d);
@ -585,17 +604,17 @@ impl Settlement {
pub fn get_color(&self, pos: Vec2<i32>) -> Option<Rgb<u8>> {
let sample = self.land.get_at_block(pos);
match sample.tower {
Some((Tower::Wall, _)) => return Some(Rgb::new(50, 50, 50)),
_ => {},
}
// match sample.tower {
// Some((Tower::Wall, _)) => return Some(Rgb::new(50, 50, 50)),
// _ => {},
// }
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)),
_ => {},
}
// 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)),
// _ => {},
// }
match sample.plot {
Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)),
@ -696,14 +715,18 @@ pub struct Land {
tiles: HashMap<Vec2<i32>, Tile>,
plots: Store<Plot>,
sampler_warp: StructureGen2d,
hazard: Id<Plot>,
}
impl Land {
pub fn new(rng: &mut impl Rng) -> Self {
let mut plots = Store::default();
let hazard = plots.insert(Plot::Hazard);
Self {
tiles: HashMap::new(),
plots: Store::default(),
plots,
sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, AREA_SIZE * 2 / 5),
hazard,
}
}
@ -797,7 +820,7 @@ impl Land {
dest: Vec2<i32>,
mut path_cost_fn: impl FnMut(Option<&Tile>, Option<&Tile>) -> f32,
) -> Option<Path<Vec2<i32>>> {
let heuristic = |pos: &Vec2<i32>| pos.distance_squared(dest) as f32;
let heuristic = |pos: &Vec2<i32>| (pos - dest).map(|e| e as f32).magnitude();
let neighbors = |pos: &Vec2<i32>| {
let pos = *pos;
CARDINALS.iter().map(move |dir| pos + *dir)
@ -868,7 +891,11 @@ impl Land {
} else {
continue;
};
if self.tile_at(tiles[0]).is_none() {
self.set(tiles[0], self.hazard);
}
let mut plots = &self.plots;
self.tiles
.get_mut(&tiles[1])
.filter(|tile| permit_fn(plots.get(tile.plot)))