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)
let block = block.or_else(|| {
chunk
.structures
.town
.as_ref()
.and_then(|town| TownGen.get((town, wpos, sample)))
});
let block = chunk
.structures
.town
.as_ref()
.and_then(|town| TownGen.get((town, wpos, sample, height)))
.or(block);
let block = structures
.iter()
@ -515,7 +514,7 @@ impl StructureInfo {
}
}
fn block_from_structure(
pub fn block_from_structure(
sblock: StructureBlock,
default_kind: BlockKind,
pos: Vec3<i32>,

View File

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

View File

@ -8,7 +8,7 @@ use common::terrain::Block;
use vek::*;
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);
}

View File

@ -1,22 +1,88 @@
use super::Generator;
use crate::{
column::ColumnSample,
block::block_from_structure,
column::{ColumnGen, ColumnSample},
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_chacha::ChaChaRng;
use std::sync::Arc;
use vek::*;
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)]
pub enum TownCell {
Empty,
Junction,
Road,
House,
Street,
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 {
@ -26,65 +92,166 @@ pub struct TownState {
}
impl TownState {
pub fn generate(center: Vec2<i32>, seed: u32, sim: &mut WorldSim) -> Option<Self> {
let center_chunk = sim.get_wpos(center)?;
pub fn generate(
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
if center_chunk.chaos > 0.5 || center_chunk.near_cliffs {
if center_chunk.chaos > 0.7 || center_chunk.near_cliffs {
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);
let mut create_road = || loop {
let junctions = grid
.iter()
.filter(|(_, cell)| {
if let TownCell::Junction = cell {
true
} else {
false
}
})
.collect::<Vec<_>>();
let mut create_road = || {
for _ in 0..10 {
let junctions = grid
.iter()
.filter(|(_, cell)| {
if let TownCell::Junction = cell {
true
} else {
false
}
})
.collect::<Vec<_>>();
// Choose an existing junction for the road to start from
let start_pos = junctions.choose(&mut sim.rng).unwrap().0; // Can't fail
// Choose an existing junction for the road to start from
let start_pos = junctions.choose(rng).unwrap().0; // Can't fail
// Choose a random direction and length for the road
let road_dir = {
let dirs = [-1, 0, 1, 0, -1];
let idx = sim.rng.gen_range(0, 4);
Vec2::new(dirs[idx], dirs[idx + 1])
};
let road_len = sim.rng.gen_range(1, 4) * 2 + 1;
// Choose a random direction and length for the road
let road_dir = {
let dirs = [-1, 0, 1, 0, -1];
let idx = rng.gen_range(0, 4);
Vec2::new(dirs[idx], dirs[idx + 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!
match grid.get(start_pos + road_dir) {
Some(TownCell::Empty) => {}
_ => 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);
// Make sure we aren't trying to create a road where a road already exists!
match grid.get(start_pos + road_dir) {
Some(TownCell::Empty) => {}
_ => continue,
}
}
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();
}
// 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 {
center,
radius,
@ -93,25 +260,64 @@ impl TownState {
}
fn get_cell(&self, wpos: Vec2<i32>) -> &TownCell {
self.grid
.get((wpos - self.center + self.radius) / CELL_SIZE)
let rpos = wpos - self.center;
match self
.grid
.get(rpos.map(|e| e.div_euclid(CELL_SIZE)) + self.grid.size() / 2)
.unwrap_or(&TownCell::Empty)
{
TownCell::PartOf(pos) => self.grid.get(*pos).unwrap(),
cell => cell,
}
}
}
pub struct 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>;
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)) {
TownCell::Road if wpos.z < sample.alt as i32 + 4 => {
Some(Block::new(BlockKind::Normal, Rgb::new(255, 200, 150)))
cell if cell.is_road() => {
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 => {
Some(Block::new(BlockKind::Normal, Rgb::new(255, 200, 250)))
TownCell::Building {
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,
}
@ -125,6 +331,6 @@ impl<'a> Generator<'a, TownState> for TownGen {
wpos: Vec2<i32>,
sample: &ColumnSample,
) -> (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,
euclidean_division,
bind_by_move_pattern_guards,
option_flattening
option_flattening,
label_break_value
)]
mod all;
@ -57,11 +58,11 @@ impl World {
pub fn sample_columns(
&self,
) -> impl Sampler<Index = Vec2<i32>, Sample = Option<ColumnSample>> + '_ {
ColumnGen::new(self)
ColumnGen::new(&self.sim)
}
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) {

View File

@ -11,6 +11,7 @@ use self::util::{
use crate::{
all::ForestKind,
column::ColumnGen,
generator::TownState,
util::{seed_expan, FastNoise, Sampler, StructureGen2d},
CONFIG,
@ -441,7 +442,8 @@ impl WorldSim {
let maybe_town = maybe_towns
.entry(*pos)
.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()
// 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 {
(pos.y * self.size.x + pos.x) as usize
fn idx(&self, pos: Vec2<i32>) -> Option<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> {
@ -22,24 +26,45 @@ impl<T: Clone> Grid<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> {
let idx = self.idx(pos);
let idx = self.idx(pos)?;
self.cells.get_mut(idx)
}
pub fn set(&mut self, pos: Vec2<i32>, cell: T) {
let idx = self.idx(pos);
self.cells.get_mut(idx).map(|c| *c = cell);
pub fn set(&mut self, pos: Vec2<i32>, cell: T) -> Option<()> {
let idx = self.idx(pos)?;
self.cells.get_mut(idx).map(|c| *c = cell)
}
pub fn iter(&self) -> impl Iterator<Item = (Vec2<i32>, &T)> + '_ {
(0..self.size.x)
.map(move |x| {
(0..self.size.y)
.map(move |y| (Vec2::new(x, y), &self.cells[self.idx(Vec2::new(x, y))]))
(0..self.size.y).map(move |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()
}