From 525cba50297c87902525a28978c0a13d6650b4a7 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 16 Apr 2020 02:40:09 +0100 Subject: [PATCH] Added dungeon rooms, corridors, mazes --- assets/voxygen/shaders/fluid-vert.glsl | 2 +- assets/voxygen/shaders/terrain-vert.glsl | 2 +- world/src/civ/mod.rs | 6 +- world/src/lib.rs | 25 ++- world/src/site/dungeon/mod.rs | 263 ++++++++++++++++++----- world/src/util/grid.rs | 2 +- world/src/util/mod.rs | 4 + 7 files changed, 227 insertions(+), 77 deletions(-) diff --git a/assets/voxygen/shaders/fluid-vert.glsl b/assets/voxygen/shaders/fluid-vert.glsl index 1bf7e172a9..cb7effea26 100644 --- a/assets/voxygen/shaders/fluid-vert.glsl +++ b/assets/voxygen/shaders/fluid-vert.glsl @@ -22,7 +22,7 @@ const float EXTRA_NEG_Z = 65536.0; void main() { f_pos = vec3((uvec3(v_pos_norm) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0x1FFFFu)) - vec3(0, 0, EXTRA_NEG_Z) + model_offs; - f_pos.z *= min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0); + f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0)); f_pos.z -= 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0); // Small waves diff --git a/assets/voxygen/shaders/terrain-vert.glsl b/assets/voxygen/shaders/terrain-vert.glsl index bfe1597ccd..8632190220 100644 --- a/assets/voxygen/shaders/terrain-vert.glsl +++ b/assets/voxygen/shaders/terrain-vert.glsl @@ -22,7 +22,7 @@ const float EXTRA_NEG_Z = 65536.0; void main() { f_pos = vec3((uvec3(v_pos_norm) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0x1FFFFu)) - vec3(0, 0, EXTRA_NEG_Z) + model_offs; - f_pos.z *= min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0); + f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0)); f_pos.z -= 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0); f_col = vec3((uvec3(v_col_light) >> uvec3(8, 16, 24)) & uvec3(0xFFu)) / 255.0; diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 31d15cf51c..68ce27ce98 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -20,7 +20,7 @@ use common::{ use crate::{ sim::{WorldSim, SimChunk}, site::{Site as WorldSite, Settlement, Dungeon}, - util::seed_expan, + util::{seed_expan, attempt}, }; const CARDINALS: [Vec2; 4] = [ @@ -41,10 +41,6 @@ const DIAGONALS: [Vec2; 8] = [ Vec2::new(-1, -1), ]; -fn attempt(max_iters: usize, mut f: impl FnMut() -> Option) -> Option { - (0..max_iters).find_map(|_| f()) -} - const INITIAL_CIV_COUNT: usize = 32; #[derive(Default)] diff --git a/world/src/lib.rs b/world/src/lib.rs index 4e78d67454..073e0f15b6 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -67,8 +67,21 @@ impl World { // TODO: misleading name mut should_continue: impl FnMut() -> bool, ) -> Result<(TerrainChunk, ChunkSupplement), ()> { + let mut sampler = self.sample_blocks(); + + let chunk_wpos2d = Vec2::from(chunk_pos) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); + let grid_border = 4; + let zcache_grid = + Grid::populate_from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2, |offs| { + sampler.get_z_cache(chunk_wpos2d - grid_border + offs) + }); + let air = Block::empty(); - let stone = Block::new(BlockKind::Dense, Rgb::new(200, 220, 255)); + let stone = Block::new(BlockKind::Dense, zcache_grid + .get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2) + .and_then(|zcache| zcache.as_ref()) + .map(|zcache| zcache.sample.stone_col) + .unwrap_or(Rgb::new(125, 120, 130))); let water = Block::new(BlockKind::Water, Rgb::new(60, 90, 190)); let _chunk_size2d = TerrainChunkSize::RECT_SIZE; @@ -97,14 +110,6 @@ impl World { }; let meta = TerrainChunkMeta::new(sim_chunk.get_name(&self.sim), sim_chunk.get_biome()); - let mut sampler = self.sample_blocks(); - - let chunk_wpos2d = Vec2::from(chunk_pos) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); - let grid_border = 4; - let zcache_grid = - Grid::populate_from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2, |offs| { - sampler.get_z_cache(chunk_wpos2d - grid_border + offs) - }); let mut chunk = TerrainChunk::new(base_z, stone, air, meta); for y in 0..TerrainChunkSize::RECT_SIZE.y as i32 { @@ -116,7 +121,7 @@ impl World { let offs = Vec2::new(x, y); let wpos2d = chunk_wpos2d + offs; - let z_cache = match zcache_grid.get(offs + grid_border) { + let z_cache = match zcache_grid.get(grid_border + offs) { Some(Some(z_cache)) => z_cache, _ => continue, }; diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 579b2698cd..b42b09db26 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -1,7 +1,7 @@ use crate::{ column::ColumnSample, sim::{SimChunk, WorldSim}, - util::{Grid, RandomField, Sampler, StructureGen2d}, + util::{attempt, Grid, RandomField, Sampler, StructureGen2d}, site::BlockMask, }; use super::SpawnRules; @@ -53,8 +53,8 @@ impl Dungeon { alt: ctx.sim.and_then(|sim| sim.get_alt_approx(wpos)).unwrap_or(0.0) as i32, noise: RandomField::new(ctx.rng.gen()), floors: (0..6) - .scan(Vec2::zero(), |stair_tile, _| { - let (floor, st) = Floor::generate(&mut ctx, *stair_tile); + .scan(Vec2::zero(), |stair_tile, level| { + let (floor, st) = Floor::generate(&mut ctx, *stair_tile, level); *stair_tile = st; Some(floor) }) @@ -95,47 +95,18 @@ impl Dungeon { }; let surface_z = col_sample.riverless_alt.floor() as i32; - let make_staircase = |pos: Vec3, radius: f32, inner_radius: f32, stretch| { - if (pos.xy().magnitude_squared() as f32) < radius.powf(2.0) { - if ((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch + pos.z as f32) % stretch < 3.0 - || (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) - { - BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(150, 150, 175)), 5) - } else { - BlockMask::new(Block::empty(), 1) - } - } else { - BlockMask::nothing() - } - }; - let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; - let mut z = self.alt + 20; + let mut z = self.alt + 6; for floor in &self.floors { - match floor.sample(tile_pos) { - Some(Tile::DownStair) | Some(Tile::Empty) => { - z -= floor.solid_depth; - for _ in 0..floor.hollow_depth { - vol.set(Vec3::new(offs.x, offs.y, z), Block::empty()); - z -= 1; - } - }, - Some(Tile::UpStair) => { - for i in 0..floor.solid_depth + floor.hollow_depth { - let rtile_pos = rpos - tile_center; - let mut block = make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), TILE_SIZE as f32 / 2.0, 1.5, 13.0); - if i >= floor.solid_depth { - block = block.resolve_with(BlockMask::new(Block::empty(), 1)); - } - if let Some(block) = block.finish() { - vol.set(Vec3::new(offs.x, offs.y, z), block); - } - z -= 1; - } - }, - None => z -= floor.solid_depth + floor.hollow_depth, + let mut sampler = floor.col_sampler(rpos); + + z -= floor.total_depth(); + for rz in 0..floor.total_depth() { + if let Some(block) = sampler(rz).finish() { + vol.set(Vec3::new(offs.x, offs.y, z + rz), block); + } } } } @@ -150,49 +121,223 @@ const CARDINALS: [Vec2; 4] = [ Vec2::new(-1, 0), ]; -const TILE_SIZE: i32 = 17; +const DIRS: [Vec2; 8] = [ + Vec2::new(0, 1), + Vec2::new(1, 0), + Vec2::new(0, -1), + Vec2::new(-1, 0), + Vec2::new(1, 1), + Vec2::new(1, -1), + Vec2::new(-1, 1), + Vec2::new(-1, -1), +]; +const TILE_SIZE: i32 = 13; + +#[derive(Clone)] pub enum Tile { UpStair, DownStair, - Empty, + Room, + Tunnel, + Solid, +} + +impl Tile { + fn is_passable(&self) -> bool { + match self { + Tile::UpStair => true, + Tile::DownStair => true, + Tile::Room => true, + Tile::Tunnel => true, + _ => false, + } + } } pub struct Floor { tile_offset: Vec2, tiles: Grid, + rooms: Vec>, solid_depth: i32, hollow_depth: i32, + stair_tile: Vec2, } +const FLOOR_SIZE: Vec2 = Vec2::new(18, 18); + impl Floor { - pub fn generate(ctx: &mut GenCtx, stair_tile: Vec2) -> (Self, Vec2) { + pub fn generate(ctx: &mut GenCtx, stair_tile: Vec2, level: i32) -> (Self, Vec2) { let new_stair_tile = std::iter::from_fn(|| Some(FLOOR_SIZE.map(|sz| ctx.rng.gen_range(-sz / 2 + 1, sz / 2)))) - .find(|pos| *pos != stair_tile) + .filter(|pos| *pos != stair_tile) + .take(8) + .max_by_key(|pos| (*pos - stair_tile).map(|e| e.abs()).sum()) .unwrap(); - const FLOOR_SIZE: Vec2 = Vec2::new(12, 12); let tile_offset = -FLOOR_SIZE / 2; - let this = Floor { + let mut this = Floor { tile_offset, - tiles: Grid::populate_from(FLOOR_SIZE, |pos| { - let tile_pos = tile_offset + pos; - if tile_pos == stair_tile { - Tile::UpStair - } else if tile_pos == new_stair_tile { - Tile::DownStair - } else { - Tile::Empty - } - }), - solid_depth: 13 * 3, + tiles: Grid::new(FLOOR_SIZE, Tile::Solid), + rooms: Vec::new(), + solid_depth: if level == 0 { + 80 + } else { + 13 * 2 + }, hollow_depth: 13, + stair_tile: new_stair_tile - tile_offset, }; + this.create_rooms(ctx, 10); + // Create routes between all rooms + let rooms = this.rooms.clone(); + for a in rooms.iter() { + for b in rooms.iter() { + this.create_route(ctx, a.center(), b.center(), true); + } + } + this.create_route(ctx, stair_tile - tile_offset, new_stair_tile - tile_offset, false); + + this.tiles.set(stair_tile - tile_offset, Tile::UpStair); + this.tiles.set(new_stair_tile - tile_offset, Tile::DownStair); (this, new_stair_tile) } - pub fn sample(&self, tile_pos: Vec2) -> Option<&Tile> { - self.tiles.get(tile_pos - self.tile_offset) + fn create_rooms(&mut self, ctx: &mut GenCtx, n: usize) { + let dim_limits = (3, 8); + + for _ in 0..n { + let room = match attempt(30, || { + let sz = Vec2::::zero().map(|_| ctx.rng.gen_range(dim_limits.0, dim_limits.1)); + let pos = FLOOR_SIZE.map2(sz, |floor_sz, room_sz| ctx.rng.gen_range(0, floor_sz + 1 - room_sz)); + let room = Rect::from((pos, Extent2::from(sz))); + let room_border = Rect::from((pos - 1, Extent2::from(sz) + 2)); // The room, but with some personal space + + // Ensure no overlap + if self.rooms + .iter() + .any(|r| r.collides_with_rect(room_border))// || r.contains_point(self.stair_tile)) + { + return None; + } + + Some(room) + }) { + Some(room) => room, + None => return, + }; + + self.rooms.push(room); + + for x in 0..room.extent().w { + for y in 0..room.extent().h { + self.tiles.set(room.position() + Vec2::new(x, y), Tile::Room); + } + } + } + } + + fn create_route(&mut self, ctx: &mut GenCtx, a: Vec2, b: Vec2, optimise_longest: bool) { + let sim = &ctx.sim; + let heuristic = move |l: &Vec2| if optimise_longest { + (l.distance_squared(b) as f32).sqrt() + } else { + 100.0 - (l.distance_squared(b) as f32).sqrt() + }; + let neighbors = |l: &Vec2| { + let l = *l; + CARDINALS + .iter() + .map(move |dir| l + dir) + .filter(|pos| self.tiles.get(*pos).is_some()) + }; + let transition = |a: &Vec2, b: &Vec2| match self.tiles.get(*b) { + Some(Tile::Room) | Some(Tile::Tunnel) => 1.0, + Some(Tile::Solid) => ctx.rng.gen_range(1.0, 10.0), + Some(Tile::UpStair) | Some(Tile::DownStair) => 0.0, + _ => 100000.0, + }; + let satisfied = |l: &Vec2| *l == b; + let mut astar = Astar::new(20000, a, heuristic); + let path = astar + .poll(FLOOR_SIZE.product() as usize + 1, heuristic, neighbors, transition, satisfied) + .into_path() + .expect("No route between locations - this shouldn't be able to happen"); + + for pos in path.iter() { + if let Some(tile @ Tile::Solid) = self.tiles.get_mut(*pos) { + *tile = Tile::Tunnel; + } + } + } + + pub fn total_depth(&self) -> i32 { + self.solid_depth + self.hollow_depth + } + + pub fn nearest_wall(&self, rpos: Vec2) -> Option> { + let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); + let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; + + DIRS + .iter() + .map(|dir| tile_pos + *dir) + .filter(|other_tile_pos| self.tiles.get(*other_tile_pos).filter(|tile| tile.is_passable()).is_none()) + .map(|other_tile_pos| rpos.clamped( + other_tile_pos * TILE_SIZE, + (other_tile_pos + 1) * TILE_SIZE - 1, + )) + .min_by_key(|nearest| rpos.distance_squared(*nearest)) + } + + pub fn col_sampler(&self, pos: Vec2) -> impl FnMut(i32) -> BlockMask + '_ { + let rpos = pos - self.tile_offset * TILE_SIZE; + let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); + let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; + let rtile_pos = rpos - tile_center; + + let empty = BlockMask::new(Block::empty(), 1); + + let make_staircase = move |pos: Vec3, radius: f32, inner_radius: f32, stretch: f32| { + let stone = BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(150, 150, 175)), 5); + + if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) { + stone + } else if (pos.xy().magnitude_squared() as f32) < radius.powf(2.0) { + if ((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch + pos.z as f32).rem_euclid(stretch) < 1.5 { + stone + } else { + empty + } + } else { + BlockMask::nothing() + } + }; + + let tunnel_dist = self.nearest_wall(rpos).map(|nearest| 1.0 - (nearest.distance_squared(rpos) as f32).sqrt() / TILE_SIZE as f32).unwrap_or(0.0); + + move |z| { + match self.tiles.get(tile_pos) { + Some(Tile::Solid) => BlockMask::nothing(), + Some(Tile::Tunnel) => { + if (z as f32) < 8.0 - 8.0 * tunnel_dist.powf(4.0) { + empty + } else { + BlockMask::nothing() + } + }, + Some(Tile::Room) | Some(Tile::DownStair) if z as f32 >= self.hollow_depth as f32 - 13.0 * tunnel_dist.powf(4.0) => BlockMask::nothing(), + Some(Tile::Room) => empty, + Some(Tile::DownStair) => make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), 0.0, 0.5, 9.0).resolve_with(empty), + Some(Tile::UpStair) => { + let mut block = make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), TILE_SIZE as f32 / 2.0, 0.5, 9.0); + if z < self.hollow_depth { + block = block.resolve_with(empty); + } + block + }, + None => BlockMask::nothing(), + } + } } } diff --git a/world/src/util/grid.rs b/world/src/util/grid.rs index 198679c8af..7c57cc2050 100644 --- a/world/src/util/grid.rs +++ b/world/src/util/grid.rs @@ -17,7 +17,7 @@ impl Grid { } } - pub fn new(default_cell: T, size: Vec2) -> Self + pub fn new(size: Vec2, default_cell: T) -> Self where T: Clone, { diff --git a/world/src/util/mod.rs b/world/src/util/mod.rs index 489e3469b2..abe9d8d0e9 100644 --- a/world/src/util/mod.rs +++ b/world/src/util/mod.rs @@ -19,3 +19,7 @@ pub use self::{ structure::StructureGen2d, unit_chooser::UnitChooser, }; + +pub fn attempt(max_iters: usize, mut f: impl FnMut() -> Option) -> Option { + (0..max_iters).find_map(|_| f()) +}