Initial implementation of towns

This commit is contained in:
Joshua Barretto 2019-08-24 23:57:55 +01:00
parent d42485238e
commit fe2ad92201
8 changed files with 321 additions and 88 deletions

Binary file not shown.

View File

@ -347,13 +347,12 @@ impl<'a> BlockGen<'a> {
}); });
// Structures (like towns) // Structures (like towns)
let block = block.or_else(|| { let block = chunk
chunk .structures
.structures .town
.town .as_ref()
.as_ref() .and_then(|town| TownGen.get((town, wpos, sample, height)))
.and_then(|town| TownGen.get((town, wpos, sample))) .or(block);
});
let block = structures let block = structures
.iter() .iter()
@ -515,7 +514,7 @@ impl StructureInfo {
} }
} }
fn block_from_structure( pub fn block_from_structure(
sblock: StructureBlock, sblock: StructureBlock,
default_kind: BlockKind, default_kind: BlockKind,
pos: Vec3<i32>, pos: Vec3<i32>,

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
all::ForestKind, all::ForestKind,
block::StructureMeta, block::StructureMeta,
sim::{LocationInfo, SimChunk}, sim::{LocationInfo, SimChunk, WorldSim},
util::{RandomPerm, Sampler, UnitChooser}, util::{RandomPerm, Sampler, UnitChooser},
World, CONFIG, World, CONFIG,
}; };
@ -20,7 +20,7 @@ use std::{
use vek::*; use vek::*;
pub struct ColumnGen<'a> { pub struct ColumnGen<'a> {
world: &'a World, pub sim: &'a WorldSim,
} }
static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7); static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7);
@ -55,14 +55,13 @@ lazy_static! {
} }
impl<'a> ColumnGen<'a> { impl<'a> ColumnGen<'a> {
pub fn new(world: &'a World) -> Self { pub fn new(sim: &'a WorldSim) -> Self {
Self { world } Self { sim }
} }
fn get_local_structure(&self, wpos: Vec2<i32>) -> Option<StructureData> { fn get_local_structure(&self, wpos: Vec2<i32>) -> Option<StructureData> {
let (pos, seed) = self let (pos, seed) = self
.world .sim
.sim()
.gen_ctx .gen_ctx
.region_gen .region_gen
.get(wpos) .get(wpos)
@ -74,7 +73,7 @@ impl<'a> ColumnGen<'a> {
let chunk_pos = pos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| { let chunk_pos = pos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| {
e / sz as i32 e / sz as i32
}); });
let chunk = self.world.sim().get(chunk_pos)?; let chunk = self.sim.get(chunk_pos)?;
if seed % 5 == 2 if seed % 5 == 2
&& chunk.temp > CONFIG.desert_temp && chunk.temp > CONFIG.desert_temp
@ -102,8 +101,7 @@ impl<'a> ColumnGen<'a> {
fn gen_close_structures(&self, wpos: Vec2<i32>) -> [Option<StructureData>; 9] { fn gen_close_structures(&self, wpos: Vec2<i32>) -> [Option<StructureData>; 9] {
let mut metas = [None; 9]; let mut metas = [None; 9];
self.world self.sim
.sim()
.gen_ctx .gen_ctx
.structure_gen .structure_gen
.get(wpos) .get(wpos)
@ -131,7 +129,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
e / sz as i32 e / sz as i32
}); });
let sim = self.world.sim(); let sim = &self.sim;
let turb = Vec2::new( let turb = Vec2::new(
sim.gen_ctx.turb_x_nz.get((wposf.div(48.0)).into_array()) as f32, sim.gen_ctx.turb_x_nz.get((wposf.div(48.0)).into_array()) as f32,
@ -383,6 +381,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.add((marble_small - 0.5) * 0.5), .add((marble_small - 0.5) * 0.5),
); );
/*
// Work out if we're on a path or near a town // Work out if we're on a path or near a town
let dist_to_path = match &sim_chunk.location { let dist_to_path = match &sim_chunk.location {
Some(loc) => { Some(loc) => {
@ -419,6 +418,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
} else { } else {
(alt, ground) (alt, ground)
}; };
*/
// Cities // Cities
// TODO: In a later MR // TODO: In a later MR

View File

@ -8,7 +8,7 @@ use common::terrain::Block;
use vek::*; use vek::*;
pub trait Generator<'a, T: 'a>: pub trait Generator<'a, T: 'a>:
Sampler<'a, Index = (&'a T, Vec3<i32>, &'a ColumnSample<'a>), Sample = Option<Block>> Sampler<'a, Index = (&'a T, Vec3<i32>, &'a ColumnSample<'a>, f32), Sample = Option<Block>>
{ {
fn get_z_limits(&self, state: &'a T, wpos: Vec2<i32>, sample: &ColumnSample) -> (f32, f32); fn get_z_limits(&self, state: &'a T, wpos: Vec2<i32>, sample: &ColumnSample) -> (f32, f32);
} }

View File

@ -1,22 +1,88 @@
use super::Generator; use super::Generator;
use crate::{ use crate::{
column::ColumnSample, block::block_from_structure,
column::{ColumnGen, ColumnSample},
sim::WorldSim, sim::WorldSim,
util::{seed_expan, Grid, Sampler}, util::{seed_expan, Grid, Sampler, UnitChooser},
}; };
use common::terrain::{Block, BlockKind}; use common::{
assets,
terrain::{Block, BlockKind, Structure},
vol::{ReadVol, Vox},
};
use lazy_static::lazy_static;
use rand::prelude::*; use rand::prelude::*;
use rand_chacha::ChaChaRng; use rand_chacha::ChaChaRng;
use std::sync::Arc;
use vek::*; use vek::*;
const CELL_SIZE: i32 = 24; const CELL_SIZE: i32 = 24;
static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x100F4E37);
lazy_static! {
pub static ref HOUSES: Vec<Arc<Structure>> =
vec![
assets::load_map("world.structure.human.house_1", |s: Structure| s
.with_center(Vec3::new(8, 10, 2)))
.unwrap(),
];
pub static ref BLACKSMITHS: Vec<Arc<Structure>> = vec![
assets::load_map("world.structure.human.blacksmith", |s: Structure| s
.with_center(Vec3::new(16, 19, 9)))
.unwrap(),
assets::load_map("world.structure.human.mage_tower", |s: Structure| s
.with_center(Vec3::new(13, 13, 4)))
.unwrap(),
];
pub static ref TOWNHALLS: Vec<Arc<Structure>> = vec![
assets::load_map("world.structure.human.town_hall_spire", |s: Structure| s
.with_center(Vec3::new(16, 16, 2)))
.unwrap(),
assets::load_map("world.structure.human.stables_1", |s: Structure| s
.with_center(Vec3::new(16, 23, 2)))
.unwrap(),
];
}
#[derive(Clone)]
pub enum Building {
House,
Blacksmith,
TownHall,
}
#[derive(Clone)] #[derive(Clone)]
pub enum TownCell { pub enum TownCell {
Empty, Empty,
Junction, Junction,
Road, Street,
House, Building {
kind: Building,
wpos: Vec3<i32>,
size: Vec2<i32>,
units: (Vec2<i32>, Vec2<i32>),
seed: u32,
},
PartOf(Vec2<i32>),
}
impl TownCell {
fn is_road(&self) -> bool {
match self {
TownCell::Junction => true,
TownCell::Street => true,
_ => false,
}
}
fn mergeable(&self) -> bool {
match self {
TownCell::Empty => true,
TownCell::Building { size, .. } if *size == Vec2::one() => true,
_ => false,
}
}
} }
pub struct TownState { pub struct TownState {
@ -26,65 +92,166 @@ pub struct TownState {
} }
impl TownState { impl TownState {
pub fn generate(center: Vec2<i32>, seed: u32, sim: &mut WorldSim) -> Option<Self> { pub fn generate(
let center_chunk = sim.get_wpos(center)?; center: Vec2<i32>,
seed: u32,
gen: &mut ColumnGen,
rng: &mut impl Rng,
) -> Option<Self> {
let center_chunk = gen.sim.get_wpos(center)?;
// First, determine whether the location is even appropriate for a town // First, determine whether the location is even appropriate for a town
if center_chunk.chaos > 0.5 || center_chunk.near_cliffs { if center_chunk.chaos > 0.7 || center_chunk.near_cliffs {
return None; return None;
} }
let radius = 150; let radius = 192;
let mut grid = Grid::new(TownCell::Empty, Vec2::broadcast(radius * 2 / CELL_SIZE)); let mut grid = Grid::new(
TownCell::Empty,
Vec2::broadcast(radius * 3 / (CELL_SIZE * 2)),
);
grid.set(grid.size() / 2, TownCell::Junction); grid.set(grid.size() / 2, TownCell::Junction);
let mut create_road = || loop { let mut create_road = || {
let junctions = grid for _ in 0..10 {
.iter() let junctions = grid
.filter(|(_, cell)| { .iter()
if let TownCell::Junction = cell { .filter(|(_, cell)| {
true if let TownCell::Junction = cell {
} else { true
false } else {
} false
}) }
.collect::<Vec<_>>(); })
.collect::<Vec<_>>();
// Choose an existing junction for the road to start from // Choose an existing junction for the road to start from
let start_pos = junctions.choose(&mut sim.rng).unwrap().0; // Can't fail let start_pos = junctions.choose(rng).unwrap().0; // Can't fail
// Choose a random direction and length for the road // Choose a random direction and length for the road
let road_dir = { let road_dir = {
let dirs = [-1, 0, 1, 0, -1]; let dirs = [-1, 0, 1, 0, -1];
let idx = sim.rng.gen_range(0, 4); let idx = rng.gen_range(0, 4);
Vec2::new(dirs[idx], dirs[idx + 1]) Vec2::new(dirs[idx], dirs[idx + 1])
}; };
let road_len = sim.rng.gen_range(1, 4) * 2 + 1; let road_len = 2 + rng.gen_range(1, 3) * 2 + 1;
// Make sure we aren't trying to create a road where a road already exists! // Make sure we aren't trying to create a road where a road already exists!
match grid.get(start_pos + road_dir) { match grid.get(start_pos + road_dir) {
Some(TownCell::Empty) => {} Some(TownCell::Empty) => {}
_ => continue, _ => continue,
}
// Pave the road
for i in 1..road_len {
let cell_pos = start_pos + road_dir * i;
if let Some(TownCell::Empty) = grid.get(cell_pos) {
grid.set(cell_pos, TownCell::Road);
} }
}
grid.set(start_pos + road_dir * road_len, TownCell::Junction);
break; // Pave the road
for i in 1..road_len {
let cell_pos = start_pos + road_dir * i;
if let Some(TownCell::Empty) = grid.get(cell_pos) {
grid.set(
cell_pos,
if i == road_len - 1 {
TownCell::Junction
} else {
TownCell::Street
},
);
} else {
grid.set(cell_pos, TownCell::Junction);
break;
}
}
break;
}
}; };
for _ in 0..8 { // Create roads
for _ in 0..12 {
create_road(); create_road();
} }
// Place houses
for x in 0..grid.size().x {
for y in 0..grid.size().y {
let pos = Vec2::new(x, y);
let wpos = center + (pos - grid.size() / 2) * CELL_SIZE + CELL_SIZE / 2;
// Is this cell near a road?
let near_road = 'near_road: {
let dirs = [-1, 0, 1, 0];
let offs = rng.gen_range(0, 4);
for i in 0..4 {
let dir = Vec2::new(dirs[(offs + i) % 4], dirs[(offs + i + 1) % 4]);
if grid.get(pos + dir).unwrap_or(&TownCell::Empty).is_road() {
break 'near_road Some(dir);
}
}
None
};
match (near_road, grid.get_mut(pos)) {
(Some(dir), Some(cell @ TownCell::Empty)) if rng.gen_range(0, 6) > 0 => {
let alt = gen.get(wpos).map(|sample| sample.alt).unwrap_or(0.0) as i32;
*cell = TownCell::Building {
kind: Building::House,
wpos: Vec3::new(wpos.x, wpos.y, alt),
size: Vec2::one(),
units: (
Vec2::new(dir.y, dir.x) * (rng.gen_range(0, 1) * 2 - 1),
-dir,
),
seed: rng.gen(),
};
}
_ => {}
}
}
}
// Merge buildings
for x in 0..grid.size().x {
for y in 0..grid.size().y {
let pos = Vec2::new(x, y);
for offx in -1..1 {
for offy in -1..1 {
if grid
.iter_area(pos + Vec2::new(offx, offy), Vec2::broadcast(2))
.any(|cell| cell.map(|(_, cell)| !cell.mergeable()).unwrap_or(true))
{
continue;
}
match grid.get_mut(pos) {
Some(TownCell::Building {
kind, wpos, size, ..
}) => {
*kind = if rng.gen() {
Building::Blacksmith
} else {
Building::TownHall
};
*wpos += Vec3::new(CELL_SIZE / 2, CELL_SIZE / 2, 0)
* (Vec2::new(offx, offy) * 2 + 1);
*size = Vec2::broadcast(2);
}
_ => continue,
}
for i in 0..2 {
for j in 0..2 {
let p = Vec2::new(i + offx, j + offy);
if pos + p != pos {
grid.set(pos + p, TownCell::PartOf(pos));
}
}
}
}
}
}
}
Some(Self { Some(Self {
center, center,
radius, radius,
@ -93,25 +260,64 @@ impl TownState {
} }
fn get_cell(&self, wpos: Vec2<i32>) -> &TownCell { fn get_cell(&self, wpos: Vec2<i32>) -> &TownCell {
self.grid let rpos = wpos - self.center;
.get((wpos - self.center + self.radius) / CELL_SIZE) match self
.grid
.get(rpos.map(|e| e.div_euclid(CELL_SIZE)) + self.grid.size() / 2)
.unwrap_or(&TownCell::Empty) .unwrap_or(&TownCell::Empty)
{
TownCell::PartOf(pos) => self.grid.get(*pos).unwrap(),
cell => cell,
}
} }
} }
pub struct TownGen; pub struct TownGen;
impl<'a> Sampler<'a> for TownGen { impl<'a> Sampler<'a> for TownGen {
type Index = (&'a TownState, Vec3<i32>, &'a ColumnSample<'a>); type Index = (&'a TownState, Vec3<i32>, &'a ColumnSample<'a>, f32);
type Sample = Option<Block>; type Sample = Option<Block>;
fn get(&self, (town, wpos, sample): Self::Index) -> Self::Sample { fn get(&self, (town, wpos, sample, height): Self::Index) -> Self::Sample {
match town.get_cell(Vec2::from(wpos)) { match town.get_cell(Vec2::from(wpos)) {
TownCell::Road if wpos.z < sample.alt as i32 + 4 => { cell if cell.is_road() => {
Some(Block::new(BlockKind::Normal, Rgb::new(255, 200, 150))) if (wpos.z as f32) < height - 1.0 {
Some(Block::new(
BlockKind::Normal,
Lerp::lerp(
Rgb::new(150.0, 120.0, 50.0),
Rgb::new(100.0, 70.0, 20.0),
sample.marble_small,
)
.map(|e| e as u8),
))
} else {
Some(Block::empty())
}
} }
TownCell::Junction if wpos.z < sample.alt as i32 + 4 => { TownCell::Building {
Some(Block::new(BlockKind::Normal, Rgb::new(255, 200, 250))) kind,
wpos: building_wpos,
units,
seed,
..
} => {
let rpos = wpos - building_wpos;
let volumes: &'static [_] = match kind {
Building::House => &HOUSES,
Building::Blacksmith => &BLACKSMITHS,
Building::TownHall => &TOWNHALLS,
};
volumes[*seed as usize % volumes.len()]
.get(
Vec3::from(units.0) * rpos.x
+ Vec3::from(units.1) * rpos.y
+ Vec3::unit_z() * rpos.z,
)
.ok()
.and_then(|sb| {
block_from_structure(*sb, BlockKind::Normal, wpos, wpos.into(), 0, sample)
})
} }
_ => None, _ => None,
} }
@ -125,6 +331,6 @@ impl<'a> Generator<'a, TownState> for TownGen {
wpos: Vec2<i32>, wpos: Vec2<i32>,
sample: &ColumnSample, sample: &ColumnSample,
) -> (f32, f32) { ) -> (f32, f32) {
(sample.alt - 32.0, sample.alt + 64.0) (sample.alt - 32.0, sample.alt + 75.0)
} }
} }

View File

@ -3,7 +3,8 @@
const_generics, const_generics,
euclidean_division, euclidean_division,
bind_by_move_pattern_guards, bind_by_move_pattern_guards,
option_flattening option_flattening,
label_break_value
)] )]
mod all; mod all;
@ -57,11 +58,11 @@ impl World {
pub fn sample_columns( pub fn sample_columns(
&self, &self,
) -> impl Sampler<Index = Vec2<i32>, Sample = Option<ColumnSample>> + '_ { ) -> impl Sampler<Index = Vec2<i32>, Sample = Option<ColumnSample>> + '_ {
ColumnGen::new(self) ColumnGen::new(&self.sim)
} }
pub fn sample_blocks(&self) -> BlockGen { pub fn sample_blocks(&self) -> BlockGen {
BlockGen::new(self, ColumnGen::new(self)) BlockGen::new(self, ColumnGen::new(&self.sim))
} }
pub fn generate_chunk(&self, chunk_pos: Vec2<i32>) -> (TerrainChunk, ChunkSupplement) { pub fn generate_chunk(&self, chunk_pos: Vec2<i32>) -> (TerrainChunk, ChunkSupplement) {

View File

@ -11,6 +11,7 @@ use self::util::{
use crate::{ use crate::{
all::ForestKind, all::ForestKind,
column::ColumnGen,
generator::TownState, generator::TownState,
util::{seed_expan, FastNoise, Sampler, StructureGen2d}, util::{seed_expan, FastNoise, Sampler, StructureGen2d},
CONFIG, CONFIG,
@ -441,7 +442,8 @@ impl WorldSim {
let maybe_town = maybe_towns let maybe_town = maybe_towns
.entry(*pos) .entry(*pos)
.or_insert_with(|| { .or_insert_with(|| {
TownState::generate(*pos, *seed, self).map(|t| Arc::new(t)) TownState::generate(*pos, *seed, &mut ColumnGen::new(self), &mut rng)
.map(|t| Arc::new(t))
}) })
.as_mut() .as_mut()
// Only care if we're close to the town // Only care if we're close to the town

View File

@ -13,8 +13,12 @@ impl<T: Clone> Grid<T> {
} }
} }
fn idx(&self, pos: Vec2<i32>) -> usize { fn idx(&self, pos: Vec2<i32>) -> Option<usize> {
(pos.y * self.size.x + pos.x) as usize if pos.map2(self.size, |e, sz| e >= 0 && e < sz).reduce_and() {
Some((pos.y * self.size.x + pos.x) as usize)
} else {
None
}
} }
pub fn size(&self) -> Vec2<i32> { pub fn size(&self) -> Vec2<i32> {
@ -22,24 +26,45 @@ impl<T: Clone> Grid<T> {
} }
pub fn get(&self, pos: Vec2<i32>) -> Option<&T> { pub fn get(&self, pos: Vec2<i32>) -> Option<&T> {
self.cells.get(self.idx(pos)) self.cells.get(self.idx(pos)?)
} }
pub fn get_mut(&mut self, pos: Vec2<i32>) -> Option<&mut T> { pub fn get_mut(&mut self, pos: Vec2<i32>) -> Option<&mut T> {
let idx = self.idx(pos); let idx = self.idx(pos)?;
self.cells.get_mut(idx) self.cells.get_mut(idx)
} }
pub fn set(&mut self, pos: Vec2<i32>, cell: T) { pub fn set(&mut self, pos: Vec2<i32>, cell: T) -> Option<()> {
let idx = self.idx(pos); let idx = self.idx(pos)?;
self.cells.get_mut(idx).map(|c| *c = cell); self.cells.get_mut(idx).map(|c| *c = cell)
} }
pub fn iter(&self) -> impl Iterator<Item = (Vec2<i32>, &T)> + '_ { pub fn iter(&self) -> impl Iterator<Item = (Vec2<i32>, &T)> + '_ {
(0..self.size.x) (0..self.size.x)
.map(move |x| { .map(move |x| {
(0..self.size.y) (0..self.size.y).map(move |y| {
.map(move |y| (Vec2::new(x, y), &self.cells[self.idx(Vec2::new(x, y))])) (
Vec2::new(x, y),
&self.cells[self.idx(Vec2::new(x, y)).unwrap()],
)
})
})
.flatten()
}
pub fn iter_area(
&self,
pos: Vec2<i32>,
size: Vec2<i32>,
) -> impl Iterator<Item = Option<(Vec2<i32>, &T)>> + '_ {
(0..size.x)
.map(move |x| {
(0..size.y).map(move |y| {
Some((
pos + Vec2::new(x, y),
&self.cells[self.idx(pos + Vec2::new(x, y))?],
))
})
}) })
.flatten() .flatten()
} }