diff --git a/assets/world/structure/human/mage_tower.vox b/assets/world/structure/human/mage_tower.vox index 7df09363c0..0d2115c4c3 100644 --- a/assets/world/structure/human/mage_tower.vox +++ b/assets/world/structure/human/mage_tower.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6ed2f574af13b79f6cb6df5450068cc637372a24a739a892d55d4b1ff4f287b -size 125468 +oid sha256:db4f7b604c35763c28aeafcfe7dc1d1722b2db77838677ffa5ef4fb08581be9d +size 41004 diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 0e95adcf93..5e8b4688b3 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -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, diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 61731051b7..34bac0c714 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -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) -> Option { 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) -> [Option; 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 diff --git a/world/src/generator/mod.rs b/world/src/generator/mod.rs index c255104205..b24d0b3d84 100644 --- a/world/src/generator/mod.rs +++ b/world/src/generator/mod.rs @@ -8,7 +8,7 @@ use common::terrain::Block; use vek::*; pub trait Generator<'a, T: 'a>: - Sampler<'a, Index = (&'a T, Vec3, &'a ColumnSample<'a>), Sample = Option> + Sampler<'a, Index = (&'a T, Vec3, &'a ColumnSample<'a>, f32), Sample = Option> { fn get_z_limits(&self, state: &'a T, wpos: Vec2, sample: &ColumnSample) -> (f32, f32); } diff --git a/world/src/generator/town.rs b/world/src/generator/town.rs index 69678e6d6f..7f578a7514 100644 --- a/world/src/generator/town.rs +++ b/world/src/generator/town.rs @@ -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> = + 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> = 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> = 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, + size: Vec2, + units: (Vec2, Vec2), + seed: u32, + }, + PartOf(Vec2), +} + +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, seed: u32, sim: &mut WorldSim) -> Option { - let center_chunk = sim.get_wpos(center)?; + pub fn generate( + center: Vec2, + seed: u32, + gen: &mut ColumnGen, + rng: &mut impl Rng, + ) -> Option { + 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::>(); + let mut create_road = || { + for _ in 0..10 { + let junctions = grid + .iter() + .filter(|(_, cell)| { + if let TownCell::Junction = cell { + true + } else { + false + } + }) + .collect::>(); - // 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) -> &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, &'a ColumnSample<'a>); + type Index = (&'a TownState, Vec3, &'a ColumnSample<'a>, f32); type Sample = Option; - 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, sample: &ColumnSample, ) -> (f32, f32) { - (sample.alt - 32.0, sample.alt + 64.0) + (sample.alt - 32.0, sample.alt + 75.0) } } diff --git a/world/src/lib.rs b/world/src/lib.rs index 69bd984cdd..cb0deb7f1f 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -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, Sample = Option> + '_ { - 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) -> (TerrainChunk, ChunkSupplement) { diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index e11a979182..5c1d1d9f2d 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -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 diff --git a/world/src/util/grid.rs b/world/src/util/grid.rs index e47995fe51..a30ca989a8 100644 --- a/world/src/util/grid.rs +++ b/world/src/util/grid.rs @@ -13,8 +13,12 @@ impl Grid { } } - fn idx(&self, pos: Vec2) -> usize { - (pos.y * self.size.x + pos.x) as usize + fn idx(&self, pos: Vec2) -> Option { + 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 { @@ -22,24 +26,45 @@ impl Grid { } pub fn get(&self, pos: Vec2) -> Option<&T> { - self.cells.get(self.idx(pos)) + self.cells.get(self.idx(pos)?) } pub fn get_mut(&mut self, pos: Vec2) -> 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, cell: T) { - let idx = self.idx(pos); - self.cells.get_mut(idx).map(|c| *c = cell); + pub fn set(&mut self, pos: Vec2, cell: T) -> Option<()> { + let idx = self.idx(pos)?; + self.cells.get_mut(idx).map(|c| *c = cell) } pub fn iter(&self) -> impl Iterator, &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, + size: Vec2, + ) -> impl Iterator, &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() }