Better town walls, made settlements avoid rivers

This commit is contained in:
Joshua Barretto 2020-02-09 21:56:52 +00:00
parent 5d5e8e3238
commit 9dc46c490e
5 changed files with 111 additions and 42 deletions

View File

@ -9,7 +9,7 @@ fn main() {
let mut win = let mut win =
minifb::Window::new("Settlement Viewer", W, H, minifb::WindowOptions::default()).unwrap(); minifb::Window::new("Settlement Viewer", W, H, minifb::WindowOptions::default()).unwrap();
let settlement = Settlement::generate(Vec2::zero(), &mut thread_rng()); let settlement = Settlement::generate(Vec2::zero(), None, &mut thread_rng());
let mut focus = Vec2::<f32>::zero(); let mut focus = Vec2::<f32>::zero();
let mut zoom = 1.0; let mut zoom = 1.0;

View File

@ -25,6 +25,12 @@ pub enum Site {
} }
impl Site { impl Site {
pub fn radius(&self) -> f32 {
match self {
Site::Settlement(settlement) => settlement.radius(),
}
}
pub fn get_surface(&self, wpos: Vec2<i32>) -> Option<Block> { pub fn get_surface(&self, wpos: Vec2<i32>) -> Option<Block> {
match self { match self {
Site::Settlement(settlement) => settlement.get_surface(wpos), Site::Settlement(settlement) => settlement.get_surface(wpos),

View File

@ -1,6 +1,7 @@
use crate::{ use crate::{
block::ZCache, block::ZCache,
util::{Grid, Sampler, StructureGen2d}, sim::{SimChunk, WorldSim},
util::{Grid, RandomField, Sampler, StructureGen2d},
}; };
use common::{ use common::{
astar::Astar, astar::Astar,
@ -65,7 +66,13 @@ pub fn center_of(p: [Vec2<f32>; 3]) -> Vec2<f32> {
Vec2::new(x, y) Vec2::new(x, y)
} }
const AREA_SIZE: u32 = 64; impl SimChunk {
fn can_host_settlement(&self) -> bool {
!self.near_cliffs && !self.river.is_river() && !self.river.is_lake()
}
}
const AREA_SIZE: u32 = 32;
fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as i32 } fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as i32 }
@ -95,7 +102,7 @@ pub struct Farm {
} }
impl Settlement { impl Settlement {
pub fn generate(wpos: Vec2<i32>, rng: &mut impl Rng) -> Self { pub fn generate(wpos: Vec2<i32>, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self {
let mut this = Self { let mut this = Self {
origin: wpos, origin: wpos,
land: Land::new(rng), land: Land::new(rng),
@ -104,6 +111,10 @@ impl Settlement {
town: None, town: None,
}; };
if let Some(sim) = sim {
this.designate_from_world(sim, rng);
}
//this.place_river(rng); //this.place_river(rng);
this.place_farms(rng); this.place_farms(rng);
@ -113,6 +124,28 @@ impl Settlement {
this this
} }
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);
Spiral2d::new()
.take_while(|tile| tile.map(|e| e.abs()).reduce_max() < tile_radius)
.for_each(|tile| {
let wpos = self.origin + tile * AREA_SIZE as i32;
if (0..4)
.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)
})
{
self.land.set(tile, hazard);
}
})
}
pub fn place_river(&mut self, rng: &mut impl Rng) { pub fn place_river(&mut self, rng: &mut impl Rng) {
let river_dir = Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5).normalized(); let river_dir = Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5).normalized();
let radius = 500.0 + rng.gen::<f32>().powf(2.0) * 1000.0; let radius = 500.0 + rng.gen::<f32>().powf(2.0) * 1000.0;
@ -145,12 +178,14 @@ impl Settlement {
} }
pub fn place_paths(&mut self, rng: &mut impl Rng) { pub fn place_paths(&mut self, rng: &mut impl Rng) {
const PATH_COUNT: usize = 6;
let mut dir = Vec2::zero(); let mut dir = Vec2::zero();
for _ in 0..6 { for _ in 0..PATH_COUNT {
dir = (Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5) * 2.0 - dir) dir = (Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5) * 2.0 - dir)
.try_normalized() .try_normalized()
.unwrap_or(Vec2::zero()); .unwrap_or(Vec2::zero());
let origin = dir.map(|e| (e * 20.0) as i32); let origin = dir.map(|e| (e * 100.0) as i32);
let origin = self let origin = self
.land .land
.find_tile_near(origin, |plot| match plot { .find_tile_near(origin, |plot| match plot {
@ -164,6 +199,7 @@ impl Settlement {
.find_path(origin, town.base_tile, |from, to| match (from, to) { .find_path(origin, town.base_tile, |from, to| match (from, to) {
(_, Some(b)) if self.land.plot(b.plot) == &Plot::Dirt => 0.0, (_, Some(b)) if self.land.plot(b.plot) == &Plot::Dirt => 0.0,
(_, Some(b)) if self.land.plot(b.plot) == &Plot::Water => 20.0, (_, Some(b)) if self.land.plot(b.plot) == &Plot::Water => 20.0,
(_, Some(b)) if self.land.plot(b.plot) == &Plot::Hazard => 50.0,
(Some(a), Some(b)) if a.contains(WayKind::Wall) => { (Some(a), Some(b)) if a.contains(WayKind::Wall) => {
if b.contains(WayKind::Wall) { if b.contains(WayKind::Wall) {
1000.0 1000.0
@ -176,22 +212,25 @@ impl Settlement {
}) })
}) { }) {
let path = path.iter().copied().collect::<Vec<_>>(); let path = path.iter().copied().collect::<Vec<_>>();
self.land.write_path(&path, WayKind::Path, |_| true, true); self.land.write_path(&path, WayKind::Path, |_| true, false);
} }
} }
} }
pub fn place_town(&mut self, rng: &mut impl Rng) { pub fn place_town(&mut self, rng: &mut impl Rng) {
const PLOT_COUNT: usize = 2;
let mut origin = Vec2::new(rng.gen_range(-2, 3), rng.gen_range(-2, 3)); let mut origin = Vec2::new(rng.gen_range(-2, 3), rng.gen_range(-2, 3));
let town = self.land.new_plot(Plot::Town); for i in 0..PLOT_COUNT {
for i in 0..6 {
if let Some(base_tile) = self.land.find_tile_near(origin, |plot| match plot { if let Some(base_tile) = self.land.find_tile_near(origin, |plot| match plot {
Some(Plot::Field { .. }) => true, Some(Plot::Field { .. }) => true,
Some(Plot::Dirt) => true, Some(Plot::Dirt) => true,
_ => false, _ => false,
}) { }) {
self.land.set(base_tile, town); self.land
.plot_at_mut(base_tile)
.map(|plot| *plot = Plot::Town);
if i == 0 { if i == 0 {
/* /*
@ -222,6 +261,7 @@ impl Settlement {
.find_path(spokes[i], spokes[(i + 1) % spokes.len()], |_, to| match to .find_path(spokes[i], spokes[(i + 1) % spokes.len()], |_, to| match to
.map(|to| self.land.plot(to.plot)) .map(|to| self.land.plot(to.plot))
{ {
Some(Plot::Hazard) => 10000.0,
Some(Plot::Town) => 1000.0, Some(Plot::Town) => 1000.0,
_ => 1.0, _ => 1.0,
}) })
@ -248,7 +288,10 @@ impl Settlement {
} }
pub fn place_farms(&mut self, rng: &mut impl Rng) { pub fn place_farms(&mut self, rng: &mut impl Rng) {
for _ in 0..6 { const FARM_COUNT: usize = 4;
const FIELDS_PER_FARM: usize = 5;
for _ in 0..FARM_COUNT {
if let Some(base_tile) = self if let Some(base_tile) = self
.land .land
.find_tile_near(Vec2::zero(), |plot| plot.is_none()) .find_tile_near(Vec2::zero(), |plot| plot.is_none())
@ -258,7 +301,7 @@ impl Settlement {
self.land.set(base_tile, farmhouse); self.land.set(base_tile, farmhouse);
// Farmhouses // Farmhouses
for _ in 0..rng.gen_range(1, 4) { for _ in 0..rng.gen_range(1, 3) {
let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2) let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2)
+ Vec2::new(rng.gen_range(-16, 16), rng.gen_range(-16, 16)); + Vec2::new(rng.gen_range(-16, 16), rng.gen_range(-16, 16));
@ -273,7 +316,7 @@ impl Settlement {
// Fields // Fields
let farmland = self.farms.insert(Farm { base_tile }); let farmland = self.farms.insert(Farm { base_tile });
for _ in 0..5 { for _ in 0..FIELDS_PER_FARM {
self.place_field(farmland, base_tile, rng); self.place_field(farmland, base_tile, rng);
} }
} }
@ -286,16 +329,16 @@ impl Settlement {
origin: Vec2<i32>, origin: Vec2<i32>,
rng: &mut impl Rng, rng: &mut impl Rng,
) -> Option<Id<Plot>> { ) -> Option<Id<Plot>> {
let max_size = 7; const MAX_FIELD_SIZE: usize = 24;
if let Some(center) = self.land.find_tile_near(origin, |plot| plot.is_none()) { if let Some(center) = self.land.find_tile_near(origin, |plot| plot.is_none()) {
let field = self.land.new_plot(Plot::Field { let field = self.land.new_plot(Plot::Field {
farm, farm,
seed: rng.gen(), seed: rng.gen(),
}); });
let tiles = self let tiles =
.land self.land
.grow_from(center, rng.gen_range(1, max_size), rng, |plot| { .grow_from(center, rng.gen_range(5, MAX_FIELD_SIZE), rng, |plot| {
plot.is_none() plot.is_none()
}); });
for pos in tiles.into_iter() { for pos in tiles.into_iter() {
@ -307,12 +350,16 @@ impl Settlement {
} }
} }
pub fn radius(&self) -> f32 { 1200.0 }
pub fn apply_to( pub fn apply_to(
&self, &self,
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
zcaches: &Grid<Option<ZCache>>, zcaches: &Grid<Option<ZCache>>,
vol: &mut (impl BaseVol<Vox = Block> + WriteVol), vol: &mut (impl BaseVol<Vox = Block> + WriteVol),
) { ) {
let rand_field = RandomField::new(0);
for y in 0..zcaches.size().y { for y in 0..zcaches.size().y {
for x in 0..zcaches.size().x { for x in 0..zcaches.size().x {
let offs = Vec2::new(x, y); let offs = Vec2::new(x, y);
@ -326,15 +373,25 @@ impl Settlement {
let wpos2d = wpos2d + offs; let wpos2d = wpos2d + offs;
match self.land.get_at_block(wpos2d - self.origin) { match self.land.get_at_block(wpos2d - self.origin) {
Sample::Way(WayKind::Wall) => { Sample::Way(WayKind::Wall, dist) => {
let color = Lerp::lerp(
Rgb::new(130i32, 100, 0),
Rgb::new(90, 70, 50),
(rand_field.get(wpos2d.into()) % 256) as f32 / 256.0,
)
.map(|e| (e % 256) as u8);
for z in 0..12 { for z in 0..12 {
if dist / WayKind::Wall.width()
< ((1.0 - z as f32 / 12.0) * 2.0).min(1.0)
{
vol.set( vol.set(
Vec3::new(offs.x, offs.y, zcache.sample.alt.floor() as i32 + z), Vec3::new(offs.x, offs.y, zcache.sample.alt.floor() as i32 + z),
Block::new(BlockKind::Normal, Rgb::new(60, 60, 60)), Block::new(BlockKind::Normal, color),
); );
} }
}
}, },
Sample::Tower(Tower::Wall) => { Sample::Tower(Tower::Wall, _pos) => {
for z in 0..16 { for z in 0..16 {
vol.set( vol.set(
Vec3::new(offs.x, offs.y, zcache.sample.alt.floor() as i32 + z), Vec3::new(offs.x, offs.y, zcache.sample.alt.floor() as i32 + z),
@ -366,11 +423,12 @@ impl Settlement {
Some(match self.land.get_at_block(pos) { Some(match self.land.get_at_block(pos) {
Sample::Wilderness => return None, Sample::Wilderness => return None,
Sample::Way(WayKind::Path) => Rgb::new(130, 100, 0), Sample::Plot(Plot::Hazard) => return None,
Sample::Way(WayKind::Hedge) => Rgb::new(0, 150, 0), Sample::Way(WayKind::Path, _) => Rgb::new(90, 70, 50),
Sample::Way(WayKind::Wall) => Rgb::new(60, 60, 60), Sample::Way(WayKind::Hedge, _) => Rgb::new(0, 150, 0),
Sample::Tower(Tower::Wall) => Rgb::new(50, 50, 50), Sample::Way(WayKind::Wall, _) => Rgb::new(60, 60, 60),
Sample::Plot(Plot::Dirt) => Rgb::new(130, 100, 0), Sample::Tower(Tower::Wall, _) => Rgb::new(50, 50, 50),
Sample::Plot(Plot::Dirt) => Rgb::new(90, 70, 50),
Sample::Plot(Plot::Grass) => Rgb::new(100, 200, 0), Sample::Plot(Plot::Grass) => Rgb::new(100, 200, 0),
Sample::Plot(Plot::Water) => Rgb::new(100, 150, 250), Sample::Plot(Plot::Water) => Rgb::new(100, 150, 250),
Sample::Plot(Plot::Town) => { Sample::Plot(Plot::Town) => {
@ -405,6 +463,7 @@ impl Settlement {
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq)]
pub enum Plot { pub enum Plot {
Hazard,
Dirt, Dirt,
Grass, Grass,
Water, Water,
@ -431,7 +490,7 @@ impl WayKind {
match self { match self {
WayKind::Path => 4.0, WayKind::Path => 4.0,
WayKind::Hedge => 1.5, WayKind::Hedge => 1.5,
WayKind::Wall => 3.5, WayKind::Wall => 2.5,
} }
} }
} }
@ -444,7 +503,7 @@ pub enum Tower {
impl Tower { impl Tower {
pub fn radius(&self) -> f32 { pub fn radius(&self) -> f32 {
match self { match self {
Tower::Wall => 8.0, Tower::Wall => 6.0,
} }
} }
} }
@ -462,8 +521,8 @@ impl Tile {
pub enum Sample<'a> { pub enum Sample<'a> {
Wilderness, Wilderness,
Plot(&'a Plot), Plot(&'a Plot),
Way(&'a WayKind), Way(&'a WayKind, f32),
Tower(&'a Tower), Tower(&'a Tower, Vec2<i32>),
} }
pub struct Land { pub struct Land {
@ -493,7 +552,7 @@ impl Land {
if let Some(tower) = center_tile.and_then(|tile| tile.tower.as_ref()) { if let Some(tower) = center_tile.and_then(|tile| tile.tower.as_ref()) {
if (neighbors[4].0.distance_squared(pos) as f32) < tower.radius().powf(2.0) { if (neighbors[4].0.distance_squared(pos) as f32) < tower.radius().powf(2.0) {
return Sample::Tower(tower); return Sample::Tower(tower, neighbors[4].0);
} }
} }
@ -504,8 +563,9 @@ impl Land {
neighbors[map[i]].0.map(|e| e as f32), neighbors[map[i]].0.map(|e| e as f32),
]; ];
if let Some(way) = center_tile.and_then(|tile| tile.ways[i].as_ref()) { if let Some(way) = center_tile.and_then(|tile| tile.ways[i].as_ref()) {
if dist_to_line(line, pos.map(|e| e as f32)) < way.width() { let dist = dist_to_line(line, pos.map(|e| e as f32));
return Sample::Way(way); if dist < way.width() {
return Sample::Way(way, dist);
} }
} }
} }

View File

@ -200,6 +200,8 @@ impl RiverData {
.map(RiverKind::is_lake) .map(RiverKind::is_lake)
.unwrap_or(false) .unwrap_or(false)
} }
pub fn near_river(&self) -> bool { self.is_river() || self.neighbor_rivers.len() > 0 }
} }
/// Draw rivers and assign them heights, widths, and velocities. Take some /// Draw rivers and assign them heights, widths, and velocities. Take some

View File

@ -1463,7 +1463,10 @@ impl WorldSim {
// println!("Town: {:?}", town); // println!("Town: {:?}", town);
//TownState::generate(pos, &mut block_gen, &mut rng).map(|t| (pos, //TownState::generate(pos, &mut block_gen, &mut rng).map(|t| (pos,
// Arc::new(t))) // Arc::new(t)))
(pos, Site::from(Settlement::generate(pos, &mut rng))) (
pos,
Site::from(Settlement::generate(pos, Some(self), &mut rng)),
)
}, },
) )
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -1478,10 +1481,8 @@ impl WorldSim {
if let Some((pos, site)) = sites if let Some((pos, site)) = sites
.iter() .iter()
.filter(|(pos, _)| { .filter(|(pos, site)| {
pos.map(|e| e as i64) pos.map(|e| e as f32).distance(wpos.map(|e| e as f32)) < site.radius()
.distance_squared(wpos.map(|e| e as i64))
< 1200i64.pow(2)
}) })
.min_by_key(|(pos, _)| wpos.distance_squared(*pos)) .min_by_key(|(pos, _)| wpos.distance_squared(*pos))
{ {