mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'aweinstock/site2dungeon' into 'master'
Convert dungeons to site2. See merge request veloren/veloren!2499
This commit is contained in:
commit
768e1c0d31
@ -557,7 +557,7 @@ impl<const AVERAGE_PALETTE: bool> VoxelImageDecoding for TriPngEncoding<AVERAGE_
|
||||
} else {
|
||||
use BlockKind::*;
|
||||
match kind {
|
||||
Air | Water | Lava => Rgb { r: 0, g: 0, b: 0 },
|
||||
Air | Water | Lava | GlowingRock => Rgb { r: 0, g: 0, b: 0 },
|
||||
Rock => Rgb {
|
||||
r: 93,
|
||||
g: 110,
|
||||
|
@ -37,6 +37,7 @@ make_case_elim!(
|
||||
Rock = 0x10,
|
||||
WeakRock = 0x11, // Explodable
|
||||
Lava = 0x12,
|
||||
GlowingRock = 0x13,
|
||||
// 0x12 <= x < 0x20 is reserved for future rocks
|
||||
Grass = 0x20, // Note: *not* the same as grass sprites
|
||||
Snow = 0x21,
|
||||
@ -181,7 +182,7 @@ impl Block {
|
||||
|
||||
#[inline]
|
||||
pub fn get_glow(&self) -> Option<u8> {
|
||||
if matches!(self.kind, BlockKind::Lava) {
|
||||
if matches!(self.kind, BlockKind::Lava | BlockKind::GlowingRock) {
|
||||
return Some(24);
|
||||
}
|
||||
match self.get_sprite()? {
|
||||
@ -248,6 +249,7 @@ impl Block {
|
||||
BlockKind::Grass => Some(0.5),
|
||||
BlockKind::WeakRock => Some(0.75),
|
||||
BlockKind::Snow => Some(0.1),
|
||||
BlockKind::Lava => None,
|
||||
_ => self.get_sprite().and_then(|sprite| match sprite {
|
||||
SpriteKind::Anvil
|
||||
| SpriteKind::Cauldron
|
||||
|
@ -11,7 +11,7 @@ use vek::*;
|
||||
|
||||
make_case_elim!(
|
||||
structure_block,
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
#[repr(u32)]
|
||||
pub enum StructureBlock {
|
||||
None = 0,
|
||||
@ -40,12 +40,13 @@ pub enum StructureError {
|
||||
OutOfBounds,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Structure {
|
||||
center: Vec3<i32>,
|
||||
base: Arc<BaseStructure>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BaseStructure {
|
||||
vol: Dyna<StructureBlock, ()>,
|
||||
default_kind: BlockKind,
|
||||
|
@ -140,6 +140,7 @@ pub trait Access {
|
||||
fn idx(pos: Vec3<i32>, sz: Vec3<u32>) -> usize;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ColumnAccess;
|
||||
|
||||
impl Access for ColumnAccess {
|
||||
|
@ -35,7 +35,7 @@ rayon = "1.5"
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
ron = { version = "0.6", default-features = false }
|
||||
assets_manager = {version = "0.4.3", features = ["ron"]}
|
||||
#inline_tweak = "1.0.2"
|
||||
# inline_tweak = "1.0.2"
|
||||
|
||||
# compression benchmarks
|
||||
lz-fear = { version = "0.1.1", optional = true }
|
||||
|
@ -10,7 +10,7 @@ use common::{
|
||||
vol::{BaseVol, ReadVol, RectSizedVol, WriteVol},
|
||||
};
|
||||
use vek::{Vec2, Vec3};
|
||||
use veloren_world::{index::Index, IndexOwned};
|
||||
use veloren_world::{index::Index, IndexOwned, Land};
|
||||
|
||||
/// This exports a dungeon (structure only, no entities or sprites) to a
|
||||
/// MagicaVoxel .vox file
|
||||
@ -22,9 +22,9 @@ fn main() -> Result {
|
||||
println!("Saving into {}", export_path);
|
||||
let mut volume = ExportVol::new();
|
||||
let index = IndexOwned::new(Index::new(seed));
|
||||
let dungeon = veloren_world::site::Dungeon::generate(
|
||||
let dungeon = veloren_world::site2::plot::Dungeon::generate(
|
||||
volume.size_xy().map(|p| p as i32 / 2),
|
||||
None,
|
||||
&Land::empty(),
|
||||
&mut rand::thread_rng(),
|
||||
);
|
||||
dungeon.apply_to(index.as_index_ref(), Vec2::new(0, 0), |_| None, &mut volume);
|
||||
|
@ -5,7 +5,7 @@ mod econ;
|
||||
use crate::{
|
||||
config::CONFIG,
|
||||
sim::{RiverKind, WorldSim},
|
||||
site::{namegen::NameGen, Castle, Dungeon, Settlement, Site as WorldSite, Tree},
|
||||
site::{namegen::NameGen, Castle, Settlement, Site as WorldSite, Tree},
|
||||
site2,
|
||||
util::{attempt, seed_expan, CARDINALS, NEIGHBORS},
|
||||
Index, Land,
|
||||
@ -193,13 +193,15 @@ impl Civs {
|
||||
SiteKind::Settlement => {
|
||||
WorldSite::settlement(Settlement::generate(wpos, Some(ctx.sim), &mut rng))
|
||||
},
|
||||
SiteKind::Dungeon => {
|
||||
WorldSite::dungeon(Dungeon::generate(wpos, Some(ctx.sim), &mut rng))
|
||||
},
|
||||
SiteKind::Dungeon => WorldSite::dungeon(site2::Site::generate_dungeon(
|
||||
&Land::from_sim(&ctx.sim),
|
||||
&mut rng,
|
||||
wpos,
|
||||
)),
|
||||
SiteKind::Castle => {
|
||||
WorldSite::castle(Castle::generate(wpos, Some(ctx.sim), &mut rng))
|
||||
},
|
||||
SiteKind::Refactor => WorldSite::refactor(site2::Site::generate(
|
||||
SiteKind::Refactor => WorldSite::refactor(site2::Site::generate_city(
|
||||
&Land::from_sim(&ctx.sim),
|
||||
&mut rng,
|
||||
wpos,
|
||||
|
@ -144,7 +144,7 @@ impl World {
|
||||
civ::SiteKind::Settlement => world_msg::SiteKind::Town,
|
||||
civ::SiteKind::Dungeon => world_msg::SiteKind::Dungeon {
|
||||
difficulty: match site.site_tmp.map(|id| &index.sites[id].kind) {
|
||||
Some(site::SiteKind::Dungeon(d)) => d.difficulty(),
|
||||
Some(site::SiteKind::Dungeon(d)) => d.dungeon_difficulty().unwrap_or(0),
|
||||
_ => 0,
|
||||
},
|
||||
},
|
||||
|
@ -1252,7 +1252,7 @@ mod tests {
|
||||
crate::site::Castle::generate(wpos, None, &mut rng),
|
||||
),
|
||||
common::terrain::site::SitesKind::Dungeon => crate::site::Site::dungeon(
|
||||
crate::site::Dungeon::generate(wpos, None, &mut rng),
|
||||
crate::site2::Site::generate_dungeon(&crate::Land::empty(), &mut rng, wpos),
|
||||
),
|
||||
// common::terrain::site::SitesKind::Settlement |
|
||||
_ => crate::site::Site::settlement(crate::site::Settlement::generate(
|
||||
|
@ -1,6 +1,5 @@
|
||||
mod block_mask;
|
||||
mod castle;
|
||||
mod dungeon;
|
||||
pub mod economy;
|
||||
pub mod namegen;
|
||||
mod settlement;
|
||||
@ -8,8 +7,7 @@ mod tree;
|
||||
|
||||
// Reexports
|
||||
pub use self::{
|
||||
block_mask::BlockMask, castle::Castle, dungeon::Dungeon, economy::Economy,
|
||||
settlement::Settlement, tree::Tree,
|
||||
block_mask::BlockMask, castle::Castle, economy::Economy, settlement::Settlement, tree::Tree,
|
||||
};
|
||||
|
||||
use crate::{column::ColumnSample, site2, Canvas};
|
||||
@ -21,7 +19,7 @@ use vek::*;
|
||||
#[derive(Deserialize)]
|
||||
pub struct Colors {
|
||||
pub castle: castle::Colors,
|
||||
pub dungeon: dungeon::Colors,
|
||||
pub dungeon: site2::plot::dungeon::Colors,
|
||||
pub settlement: settlement::Colors,
|
||||
}
|
||||
|
||||
@ -40,7 +38,7 @@ pub struct Site {
|
||||
|
||||
pub enum SiteKind {
|
||||
Settlement(Settlement),
|
||||
Dungeon(Dungeon),
|
||||
Dungeon(site2::Site),
|
||||
Castle(Castle),
|
||||
Refactor(site2::Site),
|
||||
Tree(tree::Tree),
|
||||
@ -54,7 +52,7 @@ impl Site {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dungeon(d: Dungeon) -> Self {
|
||||
pub fn dungeon(d: site2::Site) -> Self {
|
||||
Self {
|
||||
kind: SiteKind::Dungeon(d),
|
||||
economy: Economy::default(),
|
||||
@ -95,7 +93,7 @@ impl Site {
|
||||
pub fn get_origin(&self) -> Vec2<i32> {
|
||||
match &self.kind {
|
||||
SiteKind::Settlement(s) => s.get_origin(),
|
||||
SiteKind::Dungeon(d) => d.get_origin(),
|
||||
SiteKind::Dungeon(d) => d.origin,
|
||||
SiteKind::Castle(c) => c.get_origin(),
|
||||
SiteKind::Refactor(s) => s.origin,
|
||||
SiteKind::Tree(t) => t.origin,
|
||||
@ -117,7 +115,7 @@ impl Site {
|
||||
SiteKind::Settlement(s) => s.name(),
|
||||
SiteKind::Dungeon(d) => d.name(),
|
||||
SiteKind::Castle(c) => c.name(),
|
||||
SiteKind::Refactor(_) => "Town",
|
||||
SiteKind::Refactor(s) => s.name(),
|
||||
SiteKind::Tree(_) => "Giant Tree",
|
||||
}
|
||||
}
|
||||
@ -127,7 +125,7 @@ impl Site {
|
||||
let get_col = |wpos| info.col(wpos + info.wpos);
|
||||
match &self.kind {
|
||||
SiteKind::Settlement(s) => s.apply_to(canvas.index, canvas.wpos, get_col, canvas.chunk),
|
||||
SiteKind::Dungeon(d) => d.apply_to(canvas.index, canvas.wpos, get_col, canvas.chunk),
|
||||
SiteKind::Dungeon(d) => d.render(canvas, dynamic_rng),
|
||||
SiteKind::Castle(c) => c.apply_to(canvas.index, canvas.wpos, get_col, canvas.chunk),
|
||||
SiteKind::Refactor(s) => s.render(canvas, dynamic_rng),
|
||||
SiteKind::Tree(t) => t.render(canvas, dynamic_rng),
|
||||
@ -156,7 +154,7 @@ impl Site {
|
||||
};
|
||||
s.apply_supplement(dynamic_rng, wpos2d, get_column, supplement, economy)
|
||||
},
|
||||
SiteKind::Dungeon(d) => d.apply_supplement(dynamic_rng, wpos2d, get_column, supplement),
|
||||
SiteKind::Dungeon(d) => d.apply_supplement(dynamic_rng, wpos2d, supplement),
|
||||
SiteKind::Castle(c) => c.apply_supplement(dynamic_rng, wpos2d, get_column, supplement),
|
||||
SiteKind::Refactor(_) => {},
|
||||
SiteKind::Tree(_) => {},
|
||||
|
@ -1,9 +1,17 @@
|
||||
use super::*;
|
||||
use crate::util::{RandomField, Sampler};
|
||||
use crate::{
|
||||
block::block_from_structure,
|
||||
util::{RandomField, Sampler},
|
||||
};
|
||||
use common::{
|
||||
store::{Id, Store},
|
||||
terrain::{Block, BlockKind},
|
||||
terrain::{
|
||||
structure::{Structure as PrefabStructure, StructureBlock},
|
||||
Block, BlockKind,
|
||||
},
|
||||
vol::ReadVol,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -12,11 +20,18 @@ pub enum Primitive {
|
||||
|
||||
// Shapes
|
||||
Aabb(Aabb<i32>),
|
||||
Pyramid { aabb: Aabb<i32>, inset: i32 },
|
||||
Pyramid {
|
||||
aabb: Aabb<i32>,
|
||||
inset: i32,
|
||||
},
|
||||
Cylinder(Aabb<i32>),
|
||||
Cone(Aabb<i32>),
|
||||
Sphere(Aabb<i32>),
|
||||
Plane(Aabr<i32>, Vec3<i32>, Vec2<f32>),
|
||||
/// A sampling function is always a subset of another primitive to avoid
|
||||
/// needing infinite bounds
|
||||
Sampling(Id<Primitive>, Box<dyn Fn(Vec3<i32>) -> bool>),
|
||||
Prefab(PrefabStructure),
|
||||
|
||||
// Combinators
|
||||
And(Id<Primitive>, Id<Primitive>),
|
||||
@ -26,11 +41,19 @@ pub enum Primitive {
|
||||
Diff(Id<Primitive>, Id<Primitive>),
|
||||
// Operators
|
||||
Rotate(Id<Primitive>, Mat3<i32>),
|
||||
Translate(Id<Primitive>, Vec3<i32>),
|
||||
Scale(Id<Primitive>, Vec3<f32>),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Fill {
|
||||
Block(Block),
|
||||
Brick(BlockKind, Rgb<u8>, u8),
|
||||
// TODO: the offset field for Prefab is a hack that breaks the compositionality of Translate,
|
||||
// we probably need an evaluator for the primitive tree that gets which point is queried at
|
||||
// leaf nodes given an input point to make Translate/Rotate work generally
|
||||
Prefab(PrefabStructure, Vec3<i32>, u32),
|
||||
Sampling(Arc<dyn Fn(Vec3<i32>) -> Option<Block>>),
|
||||
}
|
||||
|
||||
impl Fill {
|
||||
@ -95,6 +118,8 @@ impl Fill {
|
||||
.as_()
|
||||
.dot(*gradient) as i32)
|
||||
},
|
||||
Primitive::Sampling(a, f) => self.contains_at(tree, *a, pos) && f(pos),
|
||||
Primitive::Prefab(p) => !matches!(p.get(pos), Err(_) | Ok(StructureBlock::None)),
|
||||
Primitive::And(a, b) => {
|
||||
self.contains_at(tree, *a, pos) && self.contains_at(tree, *b, pos)
|
||||
},
|
||||
@ -112,6 +137,18 @@ impl Fill {
|
||||
let diff = pos - (aabb.min + mat.cols.map(|x| x.reduce_min()));
|
||||
self.contains_at(tree, *prim, aabb.min + mat.transposed() * diff)
|
||||
},
|
||||
Primitive::Translate(prim, vec) => {
|
||||
self.contains_at(tree, *prim, pos.map2(*vec, i32::saturating_sub))
|
||||
},
|
||||
Primitive::Scale(prim, vec) => {
|
||||
let center =
|
||||
self.get_bounds(tree, *prim).center().as_::<f32>() - Vec3::broadcast(0.5);
|
||||
let fpos = pos.as_::<f32>();
|
||||
let spos = (center + ((center - fpos) / vec))
|
||||
.map(|x| x.round())
|
||||
.as_::<i32>();
|
||||
self.contains_at(tree, *prim, spos)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,6 +157,7 @@ impl Fill {
|
||||
tree: &Store<Primitive>,
|
||||
prim: Id<Primitive>,
|
||||
pos: Vec3<i32>,
|
||||
canvas: &Canvas,
|
||||
) -> Option<Block> {
|
||||
if self.contains_at(tree, prim, pos) {
|
||||
match self {
|
||||
@ -130,6 +168,20 @@ impl Fill {
|
||||
.get((pos + Vec3::new(pos.z, pos.z, 0)) / Vec3::new(2, 2, 1))
|
||||
% *range as u32) as u8,
|
||||
)),
|
||||
Fill::Prefab(p, tr, seed) => p.get(pos - tr).ok().and_then(|sb| {
|
||||
let info = canvas.info;
|
||||
let col_sample = info.col(info.wpos)?;
|
||||
block_from_structure(
|
||||
canvas.index,
|
||||
*sb,
|
||||
pos - tr,
|
||||
p.get_bounds().center().xy(),
|
||||
*seed,
|
||||
col_sample,
|
||||
Block::air,
|
||||
)
|
||||
}),
|
||||
Fill::Sampling(f) => f(pos),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@ -169,6 +221,8 @@ impl Fill {
|
||||
};
|
||||
aabb.made_valid()
|
||||
},
|
||||
Primitive::Sampling(a, _) => self.get_bounds_inner(tree, *a)?,
|
||||
Primitive::Prefab(p) => p.get_bounds(),
|
||||
Primitive::And(a, b) => or_zip_with(
|
||||
self.get_bounds_inner(tree, *a),
|
||||
self.get_bounds_inner(tree, *b),
|
||||
@ -189,6 +243,21 @@ impl Fill {
|
||||
};
|
||||
new_aabb.made_valid()
|
||||
},
|
||||
Primitive::Translate(prim, vec) => {
|
||||
let aabb = self.get_bounds_inner(tree, *prim)?;
|
||||
Aabb {
|
||||
min: aabb.min.map2(*vec, i32::saturating_add),
|
||||
max: aabb.max.map2(*vec, i32::saturating_add),
|
||||
}
|
||||
},
|
||||
Primitive::Scale(prim, vec) => {
|
||||
let aabb = self.get_bounds_inner(tree, *prim)?;
|
||||
let center = aabb.center();
|
||||
Aabb {
|
||||
min: center + ((aabb.min - center).as_::<f32>() * vec).as_::<i32>(),
|
||||
max: center + ((aabb.max - center).as_::<f32>() * vec).as_::<i32>(),
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -214,3 +283,30 @@ pub trait Structure {
|
||||
(tree, fills)
|
||||
}
|
||||
}
|
||||
/// Extend a 2d AABR to a 3d AABB
|
||||
pub fn aabr_with_z<T>(aabr: Aabr<T>, z: std::ops::Range<T>) -> Aabb<T> {
|
||||
Aabb {
|
||||
min: aabr.min.with_z(z.start),
|
||||
max: aabr.max.with_z(z.end),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Just the corners of an AABB, good for outlining stuff when debugging
|
||||
pub fn aabb_corners<F: FnMut(Primitive) -> Id<Primitive>>(
|
||||
prim: &mut F,
|
||||
aabb: Aabb<i32>,
|
||||
) -> Id<Primitive> {
|
||||
let f = |prim: &mut F, ret, vec| {
|
||||
let sub = prim(Primitive::Aabb(Aabb {
|
||||
min: aabb.min + vec,
|
||||
max: aabb.max - vec,
|
||||
}));
|
||||
prim(Primitive::Diff(ret, sub))
|
||||
};
|
||||
let mut ret = prim(Primitive::Aabb(aabb));
|
||||
ret = f(prim, ret, Vec3::new(1, 0, 0));
|
||||
ret = f(prim, ret, Vec3::new(0, 1, 0));
|
||||
ret = f(prim, ret, Vec3::new(0, 0, 1));
|
||||
ret
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
mod gen;
|
||||
mod plot;
|
||||
pub mod plot;
|
||||
mod tile;
|
||||
|
||||
use self::{
|
||||
gen::{Fill, Primitive, Structure},
|
||||
gen::{aabr_with_z, Fill, Primitive, Structure},
|
||||
plot::{Plot, PlotKind},
|
||||
tile::{HazardKind, KeepKind, Ori, RoofKind, Tile, TileGrid, TileKind, TILE_SIZE},
|
||||
};
|
||||
@ -38,6 +38,7 @@ fn reseed(rng: &mut impl Rng) -> impl Rng { ChaChaRng::from_seed(rng.gen::<[u8;
|
||||
#[derive(Default)]
|
||||
pub struct Site {
|
||||
pub(crate) origin: Vec2<i32>,
|
||||
name: String,
|
||||
tiles: TileGrid,
|
||||
plots: Store<Plot>,
|
||||
plazas: Vec<Id<Plot>>,
|
||||
@ -275,7 +276,22 @@ impl Site {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn generate(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
|
||||
pub fn name(&self) -> &str { &self.name }
|
||||
|
||||
pub fn dungeon_difficulty(&self) -> Option<u32> {
|
||||
self.plots
|
||||
.iter()
|
||||
.filter_map(|(_, plot)| {
|
||||
if let PlotKind::Dungeon(d) = &plot.kind {
|
||||
Some(d.difficulty())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.max()
|
||||
}
|
||||
|
||||
pub fn generate_dungeon(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
|
||||
let mut rng = reseed(rng);
|
||||
|
||||
let mut site = Site {
|
||||
@ -283,6 +299,40 @@ impl Site {
|
||||
..Site::default()
|
||||
};
|
||||
|
||||
site.demarcate_obstacles(land);
|
||||
let dungeon = plot::Dungeon::generate(origin, land, &mut rng);
|
||||
site.name = dungeon.name().to_string();
|
||||
let size = (dungeon.radius() / tile::TILE_SIZE as f32).ceil() as i32;
|
||||
|
||||
let aabr = Aabr {
|
||||
min: Vec2::broadcast(-size),
|
||||
max: Vec2::broadcast(size),
|
||||
};
|
||||
|
||||
let plot = site.create_plot(Plot {
|
||||
kind: PlotKind::Dungeon(dungeon),
|
||||
root_tile: aabr.center(),
|
||||
tiles: aabr_tiles(aabr).collect(),
|
||||
seed: rng.gen(),
|
||||
});
|
||||
|
||||
site.blit_aabr(aabr, Tile {
|
||||
kind: TileKind::Empty,
|
||||
plot: Some(plot),
|
||||
});
|
||||
|
||||
site
|
||||
}
|
||||
|
||||
pub fn generate_city(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
|
||||
let mut rng = reseed(rng);
|
||||
|
||||
let mut site = Site {
|
||||
origin,
|
||||
name: "Town".into(),
|
||||
..Site::default()
|
||||
};
|
||||
|
||||
site.demarcate_obstacles(land);
|
||||
|
||||
site.make_plaza(land, &mut rng);
|
||||
@ -710,6 +760,7 @@ impl Site {
|
||||
let (prim_tree, fills) = match &self.plots[plot].kind {
|
||||
PlotKind::House(house) => house.render_collect(self),
|
||||
PlotKind::Castle(castle) => castle.render_collect(self),
|
||||
PlotKind::Dungeon(dungeon) => dungeon.render_collect(self),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
@ -721,7 +772,7 @@ impl Site {
|
||||
for z in aabb.min.z..aabb.max.z {
|
||||
let pos = Vec3::new(x, y, z);
|
||||
|
||||
if let Some(block) = fill.sample_at(&prim_tree, prim, pos) {
|
||||
if let Some(block) = fill.sample_at(&prim_tree, prim, pos, &canvas) {
|
||||
canvas.set(pos, block);
|
||||
}
|
||||
}
|
||||
@ -730,9 +781,22 @@ impl Site {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_supplement(
|
||||
&self,
|
||||
dynamic_rng: &mut impl Rng,
|
||||
wpos2d: Vec2<i32>,
|
||||
supplement: &mut crate::ChunkSupplement,
|
||||
) {
|
||||
for (_, plot) in self.plots.iter() {
|
||||
if let PlotKind::Dungeon(d) = &plot.kind {
|
||||
d.apply_supplement(dynamic_rng, wpos2d, supplement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_site() -> Site { Site::generate(&Land::empty(), &mut thread_rng(), Vec2::zero()) }
|
||||
pub fn test_site() -> Site { Site::generate_city(&Land::empty(), &mut thread_rng(), Vec2::zero()) }
|
||||
|
||||
fn wpos_is_hazard(land: &Land, wpos: Vec2<i32>) -> Option<HazardKind> {
|
||||
if land
|
||||
|
@ -1,7 +1,8 @@
|
||||
mod castle;
|
||||
pub mod dungeon;
|
||||
mod house;
|
||||
|
||||
pub use self::{castle::Castle, house::House};
|
||||
pub use self::{castle::Castle, dungeon::Dungeon, house::House};
|
||||
|
||||
use super::*;
|
||||
use crate::util::DHashSet;
|
||||
@ -30,4 +31,5 @@ pub enum PlotKind {
|
||||
Plaza,
|
||||
Castle(Castle),
|
||||
Road(Path<Vec2<i32>>),
|
||||
Dungeon(Dungeon),
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ use super::SpawnRules;
|
||||
use crate::{
|
||||
block::block_from_structure,
|
||||
column::ColumnSample,
|
||||
sim::WorldSim,
|
||||
site::{namegen::NameGen, BlockMask},
|
||||
site2::{self, aabr_with_z, Fill, Primitive, Structure as SiteStructure},
|
||||
util::{attempt, Grid, RandomField, Sampler, CARDINALS, DIRS},
|
||||
IndexRef,
|
||||
IndexRef, Land,
|
||||
};
|
||||
|
||||
use common::{
|
||||
@ -22,6 +22,7 @@ use fxhash::FxHasher64;
|
||||
use lazy_static::lazy_static;
|
||||
use rand::{prelude::*, seq::SliceRandom};
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
|
||||
pub struct Dungeon {
|
||||
@ -36,7 +37,7 @@ pub struct Dungeon {
|
||||
}
|
||||
|
||||
pub struct GenCtx<'a, R: Rng> {
|
||||
sim: Option<&'a WorldSim>,
|
||||
land: &'a Land<'a>,
|
||||
rng: &'a mut R,
|
||||
}
|
||||
|
||||
@ -64,9 +65,8 @@ lazy_static! {
|
||||
}
|
||||
|
||||
impl Dungeon {
|
||||
pub fn generate(wpos: Vec2<i32>, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self {
|
||||
let mut ctx = GenCtx { sim, rng };
|
||||
|
||||
pub fn generate(wpos: Vec2<i32>, land: &Land, rng: &mut impl Rng) -> Self {
|
||||
let mut ctx = GenCtx { land, rng };
|
||||
let difficulty = DUNGEON_DISTRIBUTION
|
||||
.choose_weighted(&mut ctx.rng, |pair| pair.1)
|
||||
.map(|(difficulty, _)| *difficulty)
|
||||
@ -76,7 +76,6 @@ impl Dungeon {
|
||||
err
|
||||
)
|
||||
});
|
||||
|
||||
let floors = 3 + difficulty / 2;
|
||||
|
||||
Self {
|
||||
@ -91,11 +90,7 @@ impl Dungeon {
|
||||
}
|
||||
},
|
||||
origin: wpos - TILE_SIZE / 2,
|
||||
alt: ctx
|
||||
.sim
|
||||
.and_then(|sim| sim.get_alt_approx(wpos))
|
||||
.unwrap_or(0.0) as i32
|
||||
+ 6,
|
||||
alt: ctx.land.get_alt_approx(wpos) as i32 + 6,
|
||||
seed: ctx.rng.gen(),
|
||||
noise: RandomField::new(ctx.rng.gen()),
|
||||
floors: (0..floors)
|
||||
@ -114,7 +109,14 @@ impl Dungeon {
|
||||
|
||||
pub fn get_origin(&self) -> Vec2<i32> { self.origin }
|
||||
|
||||
pub fn radius(&self) -> f32 { 200.0 }
|
||||
pub fn radius(&self) -> f32 {
|
||||
self.floors
|
||||
.iter()
|
||||
.map(|floor| (TILE_SIZE * floor.tiles.size()).magnitude_squared())
|
||||
.max()
|
||||
.map(|d| (d as f32).sqrt() / 2.0)
|
||||
.unwrap_or(200.0)
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_update)] // TODO: Pending review in #587
|
||||
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
|
||||
@ -205,7 +207,6 @@ impl Dungeon {
|
||||
// NOTE: Used only for dynamic elements like chests and entities!
|
||||
dynamic_rng: &mut impl Rng,
|
||||
wpos2d: Vec2<i32>,
|
||||
_get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||
supplement: &mut ChunkSupplement,
|
||||
) {
|
||||
let rpos = wpos2d - self.origin;
|
||||
@ -440,7 +441,7 @@ impl Floor {
|
||||
boss: false,
|
||||
area,
|
||||
height: ctx.rng.gen_range(15..20),
|
||||
pillars: Some(4),
|
||||
pillars: Some(ctx.rng.gen_range(2..=4)),
|
||||
difficulty: self.difficulty,
|
||||
}),
|
||||
_ => self.create_room(Room {
|
||||
@ -452,7 +453,7 @@ impl Floor {
|
||||
area,
|
||||
height: ctx.rng.gen_range(10..15),
|
||||
pillars: if ctx.rng.gen_range(0..4) == 0 {
|
||||
Some(4)
|
||||
Some(ctx.rng.gen_range(2..=4))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
@ -695,23 +696,7 @@ impl Floor {
|
||||
fn total_depth(&self) -> i32 { self.solid_depth + self.hollow_depth }
|
||||
|
||||
fn nearest_wall(&self, rpos: Vec2<i32>) -> Option<Vec2<i32>> {
|
||||
let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE));
|
||||
|
||||
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))
|
||||
tilegrid_nearest_wall(&self.tiles, rpos)
|
||||
}
|
||||
|
||||
// Find orientation of a position relative to another position
|
||||
@ -1171,3 +1156,477 @@ mod tests {
|
||||
mini_boss_fallback(tile_wcenter);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tilegrid_nearest_wall(tiles: &Grid<Tile>, rpos: Vec2<i32>) -> Option<Vec2<i32>> {
|
||||
let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE));
|
||||
|
||||
DIRS.iter()
|
||||
.map(|dir| tile_pos + *dir)
|
||||
.filter(|other_tile_pos| {
|
||||
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 spiral_staircase(
|
||||
origin: Vec3<i32>,
|
||||
radius: f32,
|
||||
inner_radius: f32,
|
||||
stretch: f32,
|
||||
) -> Box<dyn Fn(Vec3<i32>) -> bool> {
|
||||
Box::new(move |pos: Vec3<i32>| {
|
||||
let pos = pos - origin;
|
||||
if (pos.xy().magnitude_squared() as f32) < inner_radius.powi(2) {
|
||||
true
|
||||
} else if (pos.xy().magnitude_squared() as f32) < radius.powi(2) {
|
||||
((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch + pos.z as f32)
|
||||
.rem_euclid(stretch)
|
||||
< 1.5
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn wall_staircase(
|
||||
origin: Vec3<i32>,
|
||||
radius: f32,
|
||||
stretch: f32,
|
||||
) -> Box<dyn Fn(Vec3<i32>) -> bool> {
|
||||
Box::new(move |pos: Vec3<i32>| {
|
||||
let pos = pos - origin;
|
||||
if (pos.x.abs().max(pos.y.abs())) as f32 > 0.6 * radius {
|
||||
((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch + pos.z as f32)
|
||||
.rem_euclid(stretch)
|
||||
< 1.0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn inscribed_polystar(
|
||||
origin: Vec2<i32>,
|
||||
radius: f32,
|
||||
sides: usize,
|
||||
) -> Box<dyn Fn(Vec3<i32>) -> bool> {
|
||||
Box::new(move |pos| {
|
||||
use std::f32::consts::TAU;
|
||||
let rpos: Vec2<f32> = pos.xy().as_() - origin.as_();
|
||||
let is_border = rpos.magnitude_squared() > (radius - 2.0).powi(2);
|
||||
let is_line = (0..sides).into_iter().any(|i| {
|
||||
let f = |j: f32| {
|
||||
let t = j * TAU / sides as f32;
|
||||
radius * Vec2::new(t.cos(), t.sin())
|
||||
};
|
||||
let line = LineSegment2 {
|
||||
start: f(i as f32),
|
||||
end: f((i + 2) as f32),
|
||||
};
|
||||
line.distance_to_point(rpos) <= 1.0
|
||||
});
|
||||
is_border || is_line
|
||||
})
|
||||
}
|
||||
|
||||
pub fn make_wall_contours(
|
||||
tiles: Arc<Grid<Tile>>,
|
||||
floor_corner: Vec2<i32>,
|
||||
floor_z: i32,
|
||||
wall_thickness: f32,
|
||||
tunnel_height: f32,
|
||||
) -> Box<dyn Fn(Vec3<i32>) -> bool> {
|
||||
Box::new(move |pos| {
|
||||
let rpos = pos.xy() - floor_corner;
|
||||
let dist_to_wall = tilegrid_nearest_wall(&tiles, rpos)
|
||||
.map(|nearest| (nearest.distance_squared(rpos) as f32).sqrt())
|
||||
.unwrap_or(TILE_SIZE as f32);
|
||||
let tunnel_dist =
|
||||
1.0 - (dist_to_wall - wall_thickness).max(0.0) / (TILE_SIZE as f32 - wall_thickness);
|
||||
dist_to_wall < wall_thickness
|
||||
|| ((pos.z - floor_z) as f32) >= tunnel_height * (1.0 - tunnel_dist.powi(4))
|
||||
})
|
||||
}
|
||||
|
||||
impl Floor {
|
||||
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Id<Primitive>, Fill)>(
|
||||
&self,
|
||||
mut prim: F,
|
||||
mut fill: G,
|
||||
dungeon: &Dungeon,
|
||||
floor_z: i32,
|
||||
) {
|
||||
// Calculate an AABB and corner for the AABB that covers the current floor.
|
||||
let floor_corner = dungeon.origin + TILE_SIZE * self.tile_offset;
|
||||
let floor_aabb = Aabb {
|
||||
min: floor_corner.with_z(floor_z),
|
||||
max: (floor_corner + TILE_SIZE * self.tiles.size())
|
||||
.with_z(floor_z + self.total_depth()),
|
||||
};
|
||||
let floor_prim = prim(Primitive::Aabb(floor_aabb));
|
||||
|
||||
// Declare the various kinds of blocks that will be used as fills
|
||||
let vacant = Block::air(SpriteKind::Empty);
|
||||
let stone = Block::new(BlockKind::Rock, Rgb::new(150, 150, 175));
|
||||
let stone_purple = Block::new(BlockKind::GlowingRock, Rgb::new(96, 0, 128));
|
||||
|
||||
// Sprites are randomly positioned and have random kinds, this primitive
|
||||
// produces a box of dots that will later get truncated to just the
|
||||
// floor, and the corresponding fill places the random kinds where the
|
||||
// mask says to
|
||||
let floor_sprite = prim(Primitive::Sampling(
|
||||
floor_prim,
|
||||
Box::new(|pos| RandomField::new(7331).chance(pos, 0.001)),
|
||||
));
|
||||
|
||||
let floor_sprite_fill = Fill::Sampling(Arc::new(|pos| {
|
||||
Some(Block::air(
|
||||
match (RandomField::new(1337).get(pos) / 2) % 30 {
|
||||
0 => SpriteKind::Apple,
|
||||
1 => SpriteKind::VeloriteFrag,
|
||||
2 => SpriteKind::Velorite,
|
||||
3..=8 => SpriteKind::Mushroom,
|
||||
9..=15 => SpriteKind::FireBowlGround,
|
||||
_ => SpriteKind::ShortGrass,
|
||||
},
|
||||
))
|
||||
}));
|
||||
|
||||
let wall_thickness = 3.0;
|
||||
let tunnel_height = if self.final_level { 16.0 } else { 8.0 };
|
||||
let pillar_thickness: i32 = 4;
|
||||
|
||||
// Several primitives and fills use the tile information for finding the nearest
|
||||
// wall, a copy of the tilegrid for the floor is stored in an Arc to
|
||||
// avoid making a copy for each primitive
|
||||
let tiles = Arc::new(self.tiles.clone());
|
||||
|
||||
// The way the ceiling is curved around corners and near hallways is intricate
|
||||
// enough that it's easiest to do with a sampling primitive, this gets
|
||||
// masked per room so that it's more efficient to query
|
||||
let wall_contours = prim(Primitive::Sampling(floor_prim, {
|
||||
let tiles = Arc::clone(&tiles);
|
||||
make_wall_contours(tiles, floor_corner, floor_z, wall_thickness, tunnel_height)
|
||||
}));
|
||||
|
||||
// The surface 1 unit thicker than the walls is used to place the torches onto
|
||||
let wall_contour_surface = prim(Primitive::Sampling(floor_prim, {
|
||||
let tiles = Arc::clone(&tiles);
|
||||
make_wall_contours(
|
||||
tiles,
|
||||
floor_corner,
|
||||
floor_z,
|
||||
wall_thickness + 1.0,
|
||||
tunnel_height - 1.0,
|
||||
)
|
||||
}));
|
||||
|
||||
// The sconces use a sampling-based fill to orient them properly relative to the
|
||||
// walls/staircases/pillars
|
||||
let light_offset: i32 = 7;
|
||||
let sconces_wall = Fill::Sampling(Arc::new(move |pos| {
|
||||
let rpos = pos.xy() - floor_corner;
|
||||
let nearest = tilegrid_nearest_wall(&tiles, rpos);
|
||||
let ori = Floor::relative_ori(rpos, nearest.unwrap_or_default());
|
||||
Block::air(SpriteKind::WallSconce).with_ori(ori)
|
||||
}));
|
||||
let sconces_inward = Fill::Sampling(Arc::new(move |pos| {
|
||||
let rpos = pos.xy() - floor_corner;
|
||||
let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE));
|
||||
let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2;
|
||||
let ori = Floor::relative_ori(rpos, tile_center);
|
||||
Block::air(SpriteKind::WallSconce).with_ori(ori)
|
||||
}));
|
||||
let sconces_outward = Fill::Sampling(Arc::new(move |pos| {
|
||||
let rpos = pos.xy() - floor_corner;
|
||||
let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE));
|
||||
let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2;
|
||||
let ori = Floor::relative_ori(tile_center, rpos);
|
||||
Block::air(SpriteKind::WallSconce).with_ori(ori)
|
||||
}));
|
||||
|
||||
// The lighting mask is a grid of thin AABB planes with the same period as the
|
||||
// tile grid, but offset by `lighting_offset`, used to space the torches
|
||||
// on the walls/pillars/staircases
|
||||
let lighting_mask = {
|
||||
let mut lighting_mask_x = prim(Primitive::Empty);
|
||||
let floor_w = floor_aabb.max.x - floor_aabb.min.x;
|
||||
for i in 0..floor_w / light_offset {
|
||||
let j = floor_corner.x + i * TILE_SIZE + light_offset;
|
||||
let plane = prim(Primitive::Aabb(Aabb {
|
||||
min: floor_aabb.min.with_x(j - 1),
|
||||
max: floor_aabb.max.with_x(j),
|
||||
}));
|
||||
lighting_mask_x = prim(Primitive::Or(plane, lighting_mask_x));
|
||||
}
|
||||
let mut lighting_mask_y = prim(Primitive::Empty);
|
||||
let floor_h = floor_aabb.max.y - floor_aabb.min.y;
|
||||
for i in 0..floor_h / light_offset {
|
||||
let j = floor_corner.y + i * TILE_SIZE + light_offset;
|
||||
let plane = prim(Primitive::Aabb(Aabb {
|
||||
min: floor_aabb.min.with_y(j - 1),
|
||||
max: floor_aabb.max.with_y(j),
|
||||
}));
|
||||
lighting_mask_y = prim(Primitive::Or(plane, lighting_mask_y));
|
||||
}
|
||||
prim(Primitive::Xor(lighting_mask_x, lighting_mask_y))
|
||||
};
|
||||
|
||||
// Declare collections of various disjoint primitives that need postprocessing
|
||||
// after handling all the local information per-tile
|
||||
let mut stairs_bb = Vec::new();
|
||||
let mut stairs = Vec::new();
|
||||
let mut pillars = Vec::new();
|
||||
let mut boss_room_center = None;
|
||||
let mut sprites = Vec::new();
|
||||
|
||||
// This loop processes the tile grid, carving out rooms and tunnels and
|
||||
// collecting stair/pillar/sprite info to place afterwards
|
||||
for (tile_pos, tile) in self.tiles.iter() {
|
||||
let tile_corner = dungeon.origin + TILE_SIZE * (self.tile_offset + tile_pos);
|
||||
let tile_aabr = Aabr {
|
||||
min: tile_corner,
|
||||
max: tile_corner + Vec2::broadcast(TILE_SIZE),
|
||||
};
|
||||
let tile_center = tile_corner + Vec2::broadcast(TILE_SIZE / 2);
|
||||
let (mut height, room) = match tile {
|
||||
Tile::UpStair(room, _) => (self.hollow_depth, Some(room)),
|
||||
Tile::DownStair(room) => (self.hollow_depth, Some(room)),
|
||||
Tile::Room(room) => (self.hollow_depth, Some(room)),
|
||||
Tile::Tunnel => (tunnel_height as i32, None),
|
||||
Tile::Solid => continue,
|
||||
};
|
||||
|
||||
// Sprites are contained to the level above the floor, and not within walls
|
||||
let sprite_layer = prim(Primitive::Aabb(aabr_with_z(
|
||||
tile_aabr,
|
||||
floor_z..floor_z + 1,
|
||||
)));
|
||||
let sprite_layer = prim(Primitive::Diff(sprite_layer, wall_contours));
|
||||
|
||||
// Lights are 2 units above the floor, and aligned with the `lighting_mask` grid
|
||||
let lighting_plane = prim(Primitive::Aabb(aabr_with_z(
|
||||
tile_aabr,
|
||||
floor_z + 1..floor_z + 2,
|
||||
)));
|
||||
let lighting_plane = prim(Primitive::And(lighting_plane, lighting_mask));
|
||||
|
||||
let mut chests = None;
|
||||
|
||||
if let Some(room) = room.map(|i| self.rooms.get(*i)) {
|
||||
height = height.min(room.height);
|
||||
if let Tile::UpStair(_, kind) = tile {
|
||||
// Construct the staircase that connects this tile to the matching DownStair
|
||||
// tile on the floor above (or to the surface if this is the top floor), and a
|
||||
// hollow bounding box to place air in
|
||||
let center = tile_center.with_z(floor_z);
|
||||
let radius = TILE_SIZE as f32 / 2.0;
|
||||
let aabb = aabr_with_z(tile_aabr, floor_z..floor_z + self.total_depth());
|
||||
let bb = prim(match kind {
|
||||
StairsKind::Spiral => Primitive::Cylinder(aabb),
|
||||
StairsKind::WallSpiral => Primitive::Aabb(aabb),
|
||||
});
|
||||
let stair = prim(Primitive::Sampling(bb, match kind {
|
||||
StairsKind::Spiral => spiral_staircase(center, radius, 0.5, 9.0),
|
||||
StairsKind::WallSpiral => wall_staircase(center, radius, 27.0),
|
||||
}));
|
||||
// Construct the lights that go inside the staircase, starting above the
|
||||
// ceiling to avoid placing them floating in mid-air
|
||||
let mut lights = prim(Primitive::Empty);
|
||||
for i in height..self.total_depth() {
|
||||
if i % 9 == 0 {
|
||||
let mut light = prim(Primitive::Aabb(Aabb {
|
||||
min: aabb.min.with_z(floor_z + i),
|
||||
max: aabb.max.with_z(floor_z + i + 1),
|
||||
}));
|
||||
let inner = prim(Primitive::Aabb(Aabb {
|
||||
min: (aabb.min + Vec3::new(1, 1, 0)).with_z(floor_z + i),
|
||||
max: (aabb.max - Vec3::new(1, 1, 0)).with_z(floor_z + i + 1),
|
||||
}));
|
||||
|
||||
light = prim(Primitive::Diff(light, inner));
|
||||
lights = prim(Primitive::Or(light, lights));
|
||||
}
|
||||
}
|
||||
lights = prim(Primitive::And(lights, lighting_mask));
|
||||
stairs_bb.push(bb);
|
||||
stairs.push((stair, lights));
|
||||
}
|
||||
if matches!(tile, Tile::Room(_) | Tile::DownStair(_)) {
|
||||
let seed = room.seed;
|
||||
let loot_density = room.loot_density;
|
||||
let difficulty = room.difficulty;
|
||||
// Place chests with a random distribution based on the room's loot density in
|
||||
// valid sprite locations, filled based on the room's difficulty
|
||||
let chest_sprite = prim(Primitive::Sampling(
|
||||
sprite_layer,
|
||||
Box::new(move |pos| RandomField::new(seed).chance(pos, loot_density * 0.5)),
|
||||
));
|
||||
let chest_sprite_fill = Fill::Block(Block::air(match difficulty {
|
||||
0 => SpriteKind::DungeonChest0,
|
||||
1 => SpriteKind::DungeonChest1,
|
||||
2 => SpriteKind::DungeonChest2,
|
||||
3 => SpriteKind::DungeonChest3,
|
||||
4 => SpriteKind::DungeonChest4,
|
||||
5 => SpriteKind::DungeonChest5,
|
||||
_ => SpriteKind::Chest,
|
||||
}));
|
||||
chests = Some((chest_sprite, chest_sprite_fill));
|
||||
|
||||
// If a room has pillars, the current tile aligns with the pillar spacing, and
|
||||
// we're not too close to a wall (i.e. the adjacent tiles are rooms and not
|
||||
// hallways/solid), place a pillar
|
||||
if room
|
||||
.pillars
|
||||
.map(|pillar_space| {
|
||||
tile_pos
|
||||
.map(|e| e.rem_euclid(pillar_space) == 0)
|
||||
.reduce_and()
|
||||
})
|
||||
.unwrap_or(false)
|
||||
&& DIRS
|
||||
.iter()
|
||||
.map(|dir| tile_pos + *dir)
|
||||
.all(|other_tile_pos| {
|
||||
matches!(self.tiles.get(other_tile_pos), Some(Tile::Room(_)))
|
||||
})
|
||||
{
|
||||
let mut pillar = prim(Primitive::Cylinder(Aabb {
|
||||
min: (tile_center - Vec2::broadcast(pillar_thickness - 1))
|
||||
.with_z(floor_z),
|
||||
max: (tile_center + Vec2::broadcast(pillar_thickness))
|
||||
.with_z(floor_z + height),
|
||||
}));
|
||||
let base = prim(Primitive::Cylinder(Aabb {
|
||||
min: (tile_center - Vec2::broadcast(1 + pillar_thickness - 1))
|
||||
.with_z(floor_z),
|
||||
max: (tile_center + Vec2::broadcast(1 + pillar_thickness))
|
||||
.with_z(floor_z + 3),
|
||||
}));
|
||||
|
||||
let scale = (pillar_thickness + 2) as f32 / pillar_thickness as f32;
|
||||
let mut lights =
|
||||
prim(Primitive::Scale(pillar, Vec2::broadcast(scale).with_z(1.0)));
|
||||
lights = prim(Primitive::And(lighting_plane, lights));
|
||||
// Only add the base (and shift the lights up) for boss-room pillars
|
||||
if room.boss {
|
||||
lights = prim(Primitive::Translate(lights, 3 * Vec3::unit_z()));
|
||||
pillar = prim(Primitive::Or(pillar, base));
|
||||
}
|
||||
pillars.push((tile_center, pillar, lights));
|
||||
}
|
||||
}
|
||||
|
||||
// Keep track of the boss room to be able to add decorations later
|
||||
if room.boss {
|
||||
boss_room_center =
|
||||
Some(floor_corner + TILE_SIZE * room.area.center() + TILE_SIZE / 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Carve out the room's air inside the walls
|
||||
let tile_air = prim(Primitive::Aabb(aabr_with_z(
|
||||
tile_aabr,
|
||||
floor_z..floor_z + height,
|
||||
)));
|
||||
let tile_air = prim(Primitive::Diff(tile_air, wall_contours));
|
||||
fill(tile_air, Fill::Block(vacant));
|
||||
|
||||
// Place torches on the walls with the aforementioned spacing
|
||||
let sconces_layer = prim(Primitive::And(tile_air, lighting_plane));
|
||||
let sconces_layer = prim(Primitive::And(sconces_layer, wall_contour_surface));
|
||||
fill(sconces_layer, sconces_wall.clone());
|
||||
|
||||
// Defer chest/floor sprite placement
|
||||
if let Some((chest_sprite, chest_sprite_fill)) = chests {
|
||||
let chest_sprite = prim(Primitive::Diff(chest_sprite, wall_contours));
|
||||
sprites.push((chest_sprite, chest_sprite_fill));
|
||||
}
|
||||
|
||||
let floor_sprite = prim(Primitive::And(sprite_layer, floor_sprite));
|
||||
sprites.push((floor_sprite, floor_sprite_fill.clone()));
|
||||
}
|
||||
|
||||
// Place a glowing purple septagonal star inscribed in a circle in the boss room
|
||||
if let Some(boss_room_center) = boss_room_center {
|
||||
let magic_circle_bb = prim(Primitive::Cylinder(Aabb {
|
||||
min: (boss_room_center - 3 * Vec2::broadcast(TILE_SIZE) / 2).with_z(floor_z - 1),
|
||||
max: (boss_room_center + 3 * Vec2::broadcast(TILE_SIZE) / 2).with_z(floor_z),
|
||||
}));
|
||||
let magic_circle = prim(Primitive::Sampling(
|
||||
magic_circle_bb,
|
||||
inscribed_polystar(boss_room_center, 1.4 * TILE_SIZE as f32, 7),
|
||||
));
|
||||
fill(magic_circle, Fill::Block(stone_purple));
|
||||
}
|
||||
|
||||
// Place pillars and pillar lights facing the pillars
|
||||
for (pos, pillar, lights) in pillars.iter() {
|
||||
// Avoid placing pillars that would cover the septagonal star
|
||||
if let Some(boss_room_center) = boss_room_center {
|
||||
if pos.distance_squared(boss_room_center) < (3 * TILE_SIZE / 2).pow(2) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
fill(*lights, sconces_inward.clone());
|
||||
fill(*pillar, Fill::Block(stone));
|
||||
}
|
||||
// Carve out space for the stairs
|
||||
for stair_bb in stairs_bb.iter() {
|
||||
fill(*stair_bb, Fill::Block(vacant));
|
||||
// Prevent sprites from floating above the stairs
|
||||
let stair_bb_up = prim(Primitive::Translate(*stair_bb, Vec3::unit_z()));
|
||||
for (sprite, _) in sprites.iter_mut() {
|
||||
*sprite = prim(Primitive::Diff(*sprite, stair_bb_up));
|
||||
}
|
||||
}
|
||||
// Place the stairs themselves, and lights within the stairwells
|
||||
for (stair, lights) in stairs.iter() {
|
||||
fill(*lights, sconces_outward.clone());
|
||||
fill(*stair, Fill::Block(stone));
|
||||
}
|
||||
// Place the sprites
|
||||
for (sprite, sprite_fill) in sprites.into_iter() {
|
||||
fill(sprite, sprite_fill);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SiteStructure for Dungeon {
|
||||
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Id<Primitive>, Fill)>(
|
||||
&self,
|
||||
_site: &site2::Site,
|
||||
mut prim: F,
|
||||
mut fill: G,
|
||||
) {
|
||||
let origin = (self.origin + Vec2::broadcast(TILE_SIZE / 2)).with_z(self.alt + ALT_OFFSET);
|
||||
|
||||
lazy_static! {
|
||||
pub static ref ENTRANCES: AssetHandle<StructuresGroup> =
|
||||
Structure::load_group("dungeon_entrances");
|
||||
}
|
||||
|
||||
let entrances = ENTRANCES.read();
|
||||
let entrance = entrances[self.seed as usize % entrances.len()].clone();
|
||||
|
||||
let entrance_prim = prim(Primitive::Prefab(entrance.clone()));
|
||||
let entrance_prim = prim(Primitive::Translate(entrance_prim, origin));
|
||||
fill(entrance_prim, Fill::Prefab(entrance, origin, self.seed));
|
||||
|
||||
let mut z = self.alt + ALT_OFFSET;
|
||||
for floor in &self.floors {
|
||||
z -= floor.total_depth();
|
||||
|
||||
floor.render(&mut prim, &mut fill, &self, z);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user