Added dungeon rooms, corridors, mazes

This commit is contained in:
Joshua Barretto 2020-04-16 02:40:09 +01:00
parent 3ffc5a7d87
commit 525cba5029
7 changed files with 227 additions and 77 deletions

View File

@ -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

View File

@ -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;

View File

@ -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<i32>; 4] = [
@ -41,10 +41,6 @@ const DIAGONALS: [Vec2<i32>; 8] = [
Vec2::new(-1, -1),
];
fn attempt<T>(max_iters: usize, mut f: impl FnMut() -> Option<T>) -> Option<T> {
(0..max_iters).find_map(|_| f())
}
const INITIAL_CIV_COUNT: usize = 32;
#[derive(Default)]

View File

@ -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,
};

View File

@ -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<i32>, 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<i32>; 4] = [
Vec2::new(-1, 0),
];
const TILE_SIZE: i32 = 17;
const DIRS: [Vec2<i32>; 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<i32>,
tiles: Grid<Tile>,
rooms: Vec<Rect<i32, i32>>,
solid_depth: i32,
hollow_depth: i32,
stair_tile: Vec2<i32>,
}
const FLOOR_SIZE: Vec2<i32> = Vec2::new(18, 18);
impl Floor {
pub fn generate(ctx: &mut GenCtx<impl Rng>, stair_tile: Vec2<i32>) -> (Self, Vec2<i32>) {
pub fn generate(ctx: &mut GenCtx<impl Rng>, stair_tile: Vec2<i32>, level: i32) -> (Self, Vec2<i32>) {
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<i32> = 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<i32>) -> Option<&Tile> {
self.tiles.get(tile_pos - self.tile_offset)
fn create_rooms(&mut self, ctx: &mut GenCtx<impl Rng>, n: usize) {
let dim_limits = (3, 8);
for _ in 0..n {
let room = match attempt(30, || {
let sz = Vec2::<i32>::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<impl Rng>, a: Vec2<i32>, b: Vec2<i32>, optimise_longest: bool) {
let sim = &ctx.sim;
let heuristic = move |l: &Vec2<i32>| if optimise_longest {
(l.distance_squared(b) as f32).sqrt()
} else {
100.0 - (l.distance_squared(b) as f32).sqrt()
};
let neighbors = |l: &Vec2<i32>| {
let l = *l;
CARDINALS
.iter()
.map(move |dir| l + dir)
.filter(|pos| self.tiles.get(*pos).is_some())
};
let transition = |a: &Vec2<i32>, b: &Vec2<i32>| 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<i32>| *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<i32>) -> Option<Vec2<i32>> {
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<i32>) -> 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<i32>, 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(),
}
}
}
}

View File

@ -17,7 +17,7 @@ impl<T> Grid<T> {
}
}
pub fn new(default_cell: T, size: Vec2<i32>) -> Self
pub fn new(size: Vec2<i32>, default_cell: T) -> Self
where
T: Clone,
{

View File

@ -19,3 +19,7 @@ pub use self::{
structure::StructureGen2d,
unit_chooser::UnitChooser,
};
pub fn attempt<T>(max_iters: usize, mut f: impl FnMut() -> Option<T>) -> Option<T> {
(0..max_iters).find_map(|_| f())
}