Progress on converting dungeons to site2. Adds Sampling and Translate primitives.

This commit is contained in:
Avi Weinstock 2021-06-15 00:45:16 -04:00
parent 46e93ba563
commit df4f536523
9 changed files with 380 additions and 32 deletions

10
Cargo.lock generated
View File

@ -2492,6 +2492,15 @@ dependencies = [
"serde",
]
[[package]]
name = "inline_tweak"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7033e97b20277cc0d043226d1940fa7719ff08d2305d1fc7421e53066d00eb4b"
dependencies = [
"lazy_static",
]
[[package]]
name = "inotify"
version = "0.7.1"
@ -6154,6 +6163,7 @@ dependencies = [
"fxhash",
"hashbrown 0.11.2",
"image",
"inline_tweak",
"itertools 0.10.0",
"lazy_static",
"lz-fear",

View File

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

View File

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

View File

@ -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.difficulty().unwrap_or(0),
_ => 0,
},
},

View File

@ -1,6 +1,5 @@
mod block_mask;
mod castle;
mod dungeon;
pub mod economy;
pub mod namegen;
mod settlement;
@ -8,7 +7,7 @@ mod tree;
// Reexports
pub use self::{
block_mask::BlockMask, castle::Castle, dungeon::Dungeon, economy::Economy,
block_mask::BlockMask, castle::Castle, economy::Economy,
settlement::Settlement, tree::Tree,
};
@ -21,7 +20,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 +39,7 @@ pub struct Site {
pub enum SiteKind {
Settlement(Settlement),
Dungeon(Dungeon),
Dungeon(site2::Site),
Castle(Castle),
Refactor(site2::Site),
Tree(tree::Tree),
@ -54,7 +53,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 +94,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 +116,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 +126,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 +155,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, get_column, supplement),
SiteKind::Castle(c) => c.apply_supplement(dynamic_rng, wpos2d, get_column, supplement),
SiteKind::Refactor(_) => {},
SiteKind::Tree(_) => {},

View File

@ -17,6 +17,7 @@ pub enum Primitive {
Cone(Aabb<i32>),
Sphere(Aabb<i32>),
Plane(Aabr<i32>, Vec3<i32>, Vec2<f32>),
Sampling(Box<dyn Fn(Vec3<i32>) -> bool>),
// Combinators
And(Id<Primitive>, Id<Primitive>),
@ -26,8 +27,10 @@ pub enum Primitive {
Diff(Id<Primitive>, Id<Primitive>),
// Operators
Rotate(Id<Primitive>, Mat3<i32>),
Translate(Id<Primitive>, Vec3<i32>),
}
#[derive(Debug)]
pub enum Fill {
Block(Block),
Brick(BlockKind, Rgb<u8>, u8),
@ -95,6 +98,7 @@ impl Fill {
.as_()
.dot(*gradient) as i32)
},
Primitive::Sampling(f) => f(pos),
Primitive::And(a, b) => {
self.contains_at(tree, *a, pos) && self.contains_at(tree, *b, pos)
},
@ -112,6 +116,9 @@ 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))
}
}
}
@ -169,6 +176,10 @@ impl Fill {
};
aabb.made_valid()
},
Primitive::Sampling(_) => Aabb {
min: Vec3::broadcast(std::i32::MIN),
max: Vec3::broadcast(std::i32::MAX),
},
Primitive::And(a, b) => or_zip_with(
self.get_bounds_inner(tree, *a),
self.get_bounds_inner(tree, *b),
@ -189,6 +200,13 @@ 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),
}
},
})
}

View File

@ -1,5 +1,5 @@
mod gen;
mod plot;
pub mod plot;
mod tile;
use self::{
@ -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,21 @@ impl Site {
});
}
pub fn generate(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
pub fn name(&self) -> &str {
&self.name
}
pub fn 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 +298,41 @@ 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 = (2.0 + rng.gen::<f32>().powf(8.0) * 3.0).round() as i32;
let size = 8;
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::Plaza,
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);
@ -702,6 +752,7 @@ impl Site {
}
}
}
tracing::info!("{:?}: {:?}", canvas.wpos(), plots.len());
let mut plots_to_render = plots.into_iter().collect::<Vec<_>>();
plots_to_render.sort_unstable();
@ -710,6 +761,11 @@ 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) => {
let (prim_tree, fills) = dungeon.render_collect(self);
tracing::info!("{:?}: {:?} {:?}", dungeon.name(), prim_tree.ids().count(), fills.len());
(prim_tree, fills)
}
_ => continue,
};
@ -732,7 +788,7 @@ impl Site {
}
}
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

View File

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

View File

@ -2,10 +2,10 @@ use super::SpawnRules;
use crate::{
block::block_from_structure,
column::ColumnSample,
sim::WorldSim,
site::{namegen::NameGen, BlockMask},
site2::{self, Primitive, Fill, Structure as SiteStructure},
util::{attempt, Grid, RandomField, Sampler, CARDINALS, DIRS},
IndexRef,
IndexRef, Land,
};
use common::{
@ -36,7 +36,7 @@ pub struct Dungeon {
}
pub struct GenCtx<'a, R: Rng> {
sim: Option<&'a WorldSim>,
land: &'a Land<'a>,
rng: &'a mut R,
}
@ -64,9 +64,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 +75,6 @@ impl Dungeon {
err
)
});
let floors = 3 + difficulty / 2;
Self {
@ -91,11 +89,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)
@ -1164,3 +1158,270 @@ mod tests {
mini_boss_fallback(tile_wcenter);
}
}
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) {
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
{
true
} else {
false
}
} 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 {
if ((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch
+ pos.z as f32)
.rem_euclid(stretch)
< 1.0
{
true
} else {
false
}
} else {
false
}
})
}
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 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 colors = &index.colors.site.dungeon;
let vacant = Block::air(SpriteKind::Empty);
//let stone = Block::new(BlockKind::Rock, colors.stone.into());
let stone_red = Block::new(BlockKind::Rock, Rgb::new(255, 0, 0));
let stone_green = Block::new(BlockKind::Rock, Rgb::new(0, 255, 0));
let stone_blue = Block::new(BlockKind::Rock, Rgb::new(0, 0, 255));
use inline_tweak::tweak;
let cutout_size = tweak!(9);
let origin = self.origin.with_z(self.alt);
let cutout = prim(Primitive::Aabb(Aabb {
min: origin - Vec2::broadcast(cutout_size * 7).with_z(self.alt-1),
max: origin + Vec2::broadcast(cutout_size * 7).with_z(100),
}));
fill(cutout, Fill::Block(vacant));
let stairs_inf = prim(Primitive::Sampling(wall_staircase(
origin,
TILE_SIZE as f32 / 2.0,
tweak!(27.0),
)));
let bounding_box = prim(Primitive::Aabb(Aabb {
min: origin - Vec3::new(8, 8, self.alt-1),
max: origin + Vec3::new(8, 8, 400),
}));
//let stairs_inf = prim(Primitive::Sampling(Box::new(|_| true)));
let stairs = prim(Primitive::And(bounding_box, stairs_inf));
let stairs_tr1 = prim(Primitive::Translate(stairs, Vec3::unit_z()));
let stairs_tr2 = prim(Primitive::Translate(stairs, Vec3::broadcast(tweak!(-16))));
/*let stairs = prim(Primitive::Cone(Aabb {
min: self.origin.with_z(self.alt) - Vec3::broadcast(16),
max: self.origin.with_z(self.alt + 100) + Vec3::broadcast(16),
}));*/
fill(stairs, Fill::Block(stone_red));
fill(stairs_tr1, Fill::Block(stone_green));
fill(stairs_tr2, Fill::Block(stone_blue));
/*let make_staircase = move |kind: &StairsKind,
pos: Vec3<i32>,
radius: f32,
inner_radius: f32,
stretch: f32,
height_limit: i32| {
match kind {
StairsKind::Spiral => make_spiral_staircase(pos, radius, inner_radius, stretch),
StairsKind::WallSpiral => {
make_wall_staircase(pos, radius, stretch * 3.0, height_limit)
},
}
};
let wall_thickness = 3.0;
let dist_to_wall = self
.nearest_wall(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);
let floor_sprite = if RandomField::new(7331).chance(Vec3::from(pos), 0.001) {
BlockMask::new(
with_sprite(
match (RandomField::new(1337).get(Vec3::from(pos)) / 2) % 30 {
0 => SpriteKind::Apple,
1 => SpriteKind::VeloriteFrag,
2 => SpriteKind::Velorite,
3..=8 => SpriteKind::Mushroom,
9..=15 => SpriteKind::FireBowlGround,
_ => SpriteKind::ShortGrass,
},
),
1,
)
} else if let Some(Tile::Room(room)) | Some(Tile::DownStair(room)) =
self.tiles.get(tile_pos)
{
let room = &self.rooms[*room];
if RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density * 0.5) {
match room.difficulty {
0 => BlockMask::new(with_sprite(SpriteKind::DungeonChest0), 1),
1 => BlockMask::new(with_sprite(SpriteKind::DungeonChest1), 1),
2 => BlockMask::new(with_sprite(SpriteKind::DungeonChest2), 1),
3 => BlockMask::new(with_sprite(SpriteKind::DungeonChest3), 1),
4 => BlockMask::new(with_sprite(SpriteKind::DungeonChest4), 1),
5 => BlockMask::new(with_sprite(SpriteKind::DungeonChest5), 1),
_ => BlockMask::new(with_sprite(SpriteKind::Chest), 1),
}
} else {
vacant
}
} else {
vacant
};
let tunnel_height = if self.final_level { 16.0 } else { 8.0 };
let pillar_thickness: i32 = 4;
move |z| match self.tiles.get(tile_pos) {
Some(Tile::Solid) => BlockMask::nothing(),
Some(Tile::Tunnel) => {
let light_offset: i32 = 7;
if (dist_to_wall - wall_thickness) as i32 == 1
&& rtile_pos.map(|e| e % light_offset == 0).reduce_bitxor()
&& z == 1
{
let ori =
Floor::relative_ori(rpos, self.nearest_wall(rpos).unwrap_or_default());
let furniture = SpriteKind::WallSconce;
BlockMask::new(Block::air(furniture).with_ori(ori).unwrap(), 1)
} else if dist_to_wall >= wall_thickness
&& (z as f32) < tunnel_height * (1.0 - tunnel_dist.powi(4))
{
if z == 0 { floor_sprite } else { vacant }
} else {
BlockMask::nothing()
}
},
Some(Tile::Room(room)) | Some(Tile::DownStair(room))
if dist_to_wall < wall_thickness
|| z as f32
>= self.rooms[*room].height as f32 * (1.0 - tunnel_dist.powi(4)) =>
{
BlockMask::nothing()
},
Some(Tile::Room(room)) | Some(Tile::DownStair(room))
if self.rooms[*room]
.pillars
.map(|pillar_space| {
tile_pos
.map(|e| e.rem_euclid(pillar_space) == 0)
.reduce_and()
&& rtile_pos.map(|e| e as f32).magnitude_squared()
< (pillar_thickness as f32 + 0.5).powi(2)
})
.unwrap_or(false) =>
{
if z == 1 && rtile_pos.product() == 0 && rtile_pos.sum().abs() == pillar_thickness {
let ori = Floor::relative_ori(rtile_pos, Vec2::zero());
let furniture = SpriteKind::WallSconce;
BlockMask::new(Block::air(furniture).with_ori(ori).unwrap(), 1)
} else if z < self.rooms[*room].height
&& rtile_pos.map(|e| e as f32).magnitude_squared()
> (pillar_thickness as f32 - 0.5).powi(2)
{
vacant
} else {
BlockMask::nothing()
}
}
Some(Tile::Room(_)) => {
let light_offset = 7;
if z == 0 {
floor_sprite
} else if dist_to_wall as i32 == 4
&& rtile_pos.map(|e| e % light_offset == 0).reduce_bitxor()
&& z == 1
{
let ori = Floor::relative_ori(
rpos,
self.nearest_wall(rpos).unwrap_or_else(Vec2::zero),
);
let furniture = SpriteKind::WallSconce;
BlockMask::new(Block::air(furniture).with_ori(ori).unwrap(), 1)
} else {
vacant
}
},
Some(Tile::DownStair(_)) => vacant,
Some(Tile::UpStair(room, kind)) => {
let inner_radius: f32 = 0.5;
let stretch = 9;
let block = make_staircase(
kind,
Vec3::new(rtile_pos.x, rtile_pos.y, z),
TILE_SIZE as f32 / 2.0,
inner_radius,
stretch as f32,
self.total_depth(),
);
let furniture = SpriteKind::WallSconce;
let ori = Floor::relative_ori(Vec2::zero(), rtile_pos);
if z < self.rooms[*room].height {
block.resolve_with(vacant)
} else if z % stretch == 0 && rtile_pos.x == 0 && rtile_pos.y == -TILE_SIZE / 2 {
BlockMask::new(Block::air(furniture).with_ori(ori).unwrap(), 1)
} else {
make_staircase(
kind,
Vec3::new(rtile_pos.x, rtile_pos.y, z),
TILE_SIZE as f32 / 2.0,
inner_radius,
stretch as f32,
self.total_depth(),
)
}
},
None => BlockMask::nothing(),
}*/
}
}