New CSG API for site2

This commit is contained in:
Joshua Barretto 2021-09-19 18:24:14 +01:00
parent e3ab8fc8ce
commit 4acffb9cfb
12 changed files with 803 additions and 474 deletions

View File

@ -823,6 +823,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
// NOTE: To disable warp, uncomment this line.
// let warp_factor = 0.0;
let warp_factor = warp_factor
* sim_chunk
.sites
.iter()
.map(|site| index.sites[*site].spawn_rules(wpos).max_warp)
.fold(1.0f32, |a, b| a.min(b));
let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor);
let alt = alt + riverless_alt_delta;
let basement =

View File

@ -7,7 +7,12 @@
)]
#![allow(clippy::branches_sharing_code)] // TODO: evaluate
#![deny(clippy::clone_on_ref_ptr)]
#![feature(bool_to_option, const_panic, label_break_value)]
#![feature(
bool_to_option,
const_panic,
label_break_value,
option_zip
)]
mod all;
mod block;

View File

@ -25,10 +25,16 @@ pub struct Colors {
pub struct SpawnRules {
pub trees: bool,
pub max_warp: f32,
}
impl Default for SpawnRules {
fn default() -> Self { Self { trees: true } }
fn default() -> Self {
Self {
trees: true,
max_warp: 1.0,
}
}
}
pub struct Site {

View File

@ -38,6 +38,7 @@ impl Tree {
let trunk_radius = 48i32;
SpawnRules {
trees: wpos.distance_squared(self.origin) > trunk_radius.pow(2),
..SpawnRules::default()
}
}

View File

@ -11,7 +11,7 @@ use common::{
},
vol::ReadVol,
};
use std::sync::Arc;
use std::{cell::RefCell, sync::Arc};
use vek::*;
#[allow(dead_code)]
@ -39,8 +39,8 @@ pub enum Primitive {
Cone(Aabb<i32>),
Sphere(Aabb<i32>),
Plane(Aabr<i32>, Vec3<i32>, Vec2<f32>),
/// A line segment from start to finish point
Segment(LineSegment3<i32>),
/// A line segment from start to finish point with a given radius
Segment(LineSegment3<i32>, 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>),
@ -58,8 +58,43 @@ pub enum Primitive {
Scale(Id<Primitive>, Vec3<f32>),
}
impl Primitive {
pub fn and(a: impl Into<Id<Primitive>>, b: impl Into<Id<Primitive>>) -> Self {
Self::And(a.into(), b.into())
}
pub fn or(a: impl Into<Id<Primitive>>, b: impl Into<Id<Primitive>>) -> Self {
Self::Or(a.into(), b.into())
}
pub fn xor(a: impl Into<Id<Primitive>>, b: impl Into<Id<Primitive>>) -> Self {
Self::Xor(a.into(), b.into())
}
pub fn diff(a: impl Into<Id<Primitive>>, b: impl Into<Id<Primitive>>) -> Self {
Self::Diff(a.into(), b.into())
}
pub fn sampling(a: impl Into<Id<Primitive>>, f: Box<dyn Fn(Vec3<i32>) -> bool>) -> Self {
Self::Sampling(a.into(), f.into())
}
pub fn rotate(a: impl Into<Id<Primitive>>, rot: Mat3<i32>) -> Self {
Self::Rotate(a.into(), rot)
}
pub fn translate(a: impl Into<Id<Primitive>>, trans: Vec3<i32>) -> Self {
Self::Translate(a.into(), trans)
}
pub fn scale(a: impl Into<Id<Primitive>>, scale: Vec3<f32>) -> Self {
Self::Scale(a.into(), scale)
}
}
#[derive(Clone)]
pub enum Fill {
Sprite(SpriteKind),
Block(Block),
Brick(BlockKind, Rgb<u8>, u8),
// TODO: the offset field for Prefab is a hack that breaks the compositionality of Translate,
@ -180,11 +215,12 @@ impl Fill {
.as_()
.dot(*gradient) as i32)
},
Primitive::Segment(segment) => {
(segment.start.x..segment.end.x).contains(&pos.x)
&& (segment.start.y..segment.end.y).contains(&pos.y)
&& (segment.start.z..segment.end.z).contains(&pos.z)
&& segment.as_().distance_to_point(pos.map(|e| e as f32)) < 0.75
Primitive::Segment(segment, radius) => {
/*(segment.start.x..segment.end.x).contains(&pos.x)
&& (segment.start.y..segment.end.y).contains(&pos.y)
&& (segment.start.z..segment.end.z).contains(&pos.z)
&&*/
segment.as_().distance_to_point(pos.map(|e| e as f32)) < radius - 0.25
},
Primitive::Sampling(a, f) => self.contains_at(tree, *a, pos) && f(pos),
Primitive::Prefab(p) => !matches!(p.get(pos), Err(_) | Ok(StructureBlock::None)),
@ -226,10 +262,16 @@ impl Fill {
prim: Id<Primitive>,
pos: Vec3<i32>,
canvas_info: &crate::CanvasInfo,
old_block: Block,
) -> Option<Block> {
if self.contains_at(tree, prim, pos) {
match self {
Fill::Block(block) => Some(*block),
Fill::Sprite(sprite) => Some(if old_block.is_filled() {
Block::air(*sprite)
} else {
old_block.with_sprite(*sprite)
}),
Fill::Brick(bk, col, range) => Some(Block::new(
*bk,
*col + (RandomField::new(13)
@ -290,9 +332,9 @@ impl Fill {
};
aabb.made_valid()
},
Primitive::Segment(segment) => Aabb {
min: segment.start,
max: segment.end,
Primitive::Segment(segment, radius) => Aabb {
min: segment.start - radius.floor() as i32,
max: segment.end + radius.ceil() as i32,
},
Primitive::Sampling(a, _) => self.get_bounds_inner(tree, *a)?,
Primitive::Prefab(p) => p.get_bounds(),
@ -340,14 +382,87 @@ impl Fill {
}
}
pub struct Painter {
prims: RefCell<Store<Primitive>>,
fills: RefCell<Vec<(Id<Primitive>, Fill)>>,
}
impl Painter {
pub fn aabb(&self, aabb: Aabb<i32>) -> PrimitiveRef { self.prim(Primitive::Aabb(aabb)) }
pub fn line(&self, a: Vec3<i32>, b: Vec3<i32>, radius: f32) -> PrimitiveRef {
self.prim(Primitive::Segment(
LineSegment3 { start: a, end: b },
radius,
))
}
pub fn sprite(&self, pos: Vec3<i32>, sprite: SpriteKind) {
self.aabb(Aabb {
min: pos,
max: pos + 1,
})
.fill(Fill::Sprite(sprite))
}
pub fn pyramid(&self, aabb: Aabb<i32>) -> PrimitiveRef {
let inset = 0;
self.prim(Primitive::Ramp {
aabb,
inset,
dir: 0,
})
.union(self.prim(Primitive::Ramp {
aabb,
inset,
dir: 1,
}))
.union(self.prim(Primitive::Ramp {
aabb,
inset,
dir: 2,
}))
.union(self.prim(Primitive::Ramp {
aabb,
inset,
dir: 3,
}))
}
pub fn prim(&self, prim: Primitive) -> PrimitiveRef {
PrimitiveRef {
id: self.prims.borrow_mut().insert(prim),
painter: self,
}
}
pub fn fill(&self, prim: impl Into<Id<Primitive>>, fill: Fill) {
self.fills.borrow_mut().push((prim.into(), fill));
}
}
#[derive(Copy, Clone)]
pub struct PrimitiveRef<'a> {
id: Id<Primitive>,
painter: &'a Painter,
}
impl<'a> Into<Id<Primitive>> for PrimitiveRef<'a> {
fn into(self) -> Id<Primitive> { self.id }
}
impl<'a> PrimitiveRef<'a> {
pub fn union(self, other: impl Into<Id<Primitive>>) -> PrimitiveRef<'a> {
self.painter.prim(Primitive::and(self, other))
}
pub fn fill(self, fill: Fill) { self.painter.fill(self, fill); }
pub fn clear(self) { self.painter.fill(self, Fill::Block(Block::empty())); }
}
pub trait Structure {
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Id<Primitive>, Fill)>(
&self,
site: &Site,
land: &Land,
prim: F,
fill: G,
);
fn render(&self, site: &Site, land: &Land, painter: &Painter);
// Generate a primitive tree and fills for this structure
fn render_collect(
@ -355,10 +470,13 @@ pub trait Structure {
site: &Site,
land: &Land,
) -> (Store<Primitive>, Vec<(Id<Primitive>, Fill)>) {
let mut tree = Store::default();
let mut fills = Vec::new();
self.render(site, land, |p| tree.insert(p), |p, f| fills.push((p, f)));
(tree, fills)
let painter = Painter {
prims: RefCell::new(Store::default()),
fills: RefCell::new(Vec::new()),
};
self.render(site, land, &painter);
(painter.prims.into_inner(), painter.fills.into_inner())
}
}
/// Extend a 2d AABR to a 3d AABB

View File

@ -4,7 +4,7 @@ mod tile;
use self::tile::{HazardKind, KeepKind, Ori, RoofKind, Tile, TileGrid, TileKind, TILE_SIZE};
pub use self::{
gen::{aabr_with_z, Fill, Primitive, Structure},
gen::{aabr_with_z, Fill, Painter, Primitive, Structure},
plot::{Plot, PlotKind},
};
use crate::{
@ -59,11 +59,13 @@ impl Site {
}
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
let not_near_things = SQUARE_9.iter().all(|&rpos| {
self.wpos_tile(wpos + rpos * tile::TILE_SIZE as i32)
.is_empty()
});
SpawnRules {
trees: SQUARE_9.iter().all(|&rpos| {
self.wpos_tile(wpos + rpos * tile::TILE_SIZE as i32)
.is_empty()
}),
trees: not_near_things,
max_warp: if not_near_things { 1.0 } else { 0.0 },
}
}
@ -154,6 +156,7 @@ impl Site {
w,
},
plot: Some(plot),
hard_alt: Some(land.get_alt_approx(self.tile_center_wpos(tile)) as i32),
});
}
}
@ -226,6 +229,8 @@ impl Site {
})
.unwrap_or_else(Vec2::zero);
let plaza_alt = land.get_alt_approx(self.tile_center_wpos(pos)) as i32;
let aabr = Aabr {
min: pos + Vec2::broadcast(-plaza_radius),
max: pos + Vec2::broadcast(plaza_radius + 1),
@ -240,11 +245,12 @@ impl Site {
self.blit_aabr(aabr, Tile {
kind: TileKind::Plaza,
plot: Some(plaza),
hard_alt: Some(plaza_alt),
});
let mut already_pathed = vec![];
// One major, one minor road
for i in (0..rng.gen_range(1.5..2.5) as u16).rev() {
for i in (0..rng.gen_range(1.25..2.25) as u16).rev() {
if let Some(&p) = self
.plazas
.iter()
@ -335,6 +341,7 @@ impl Site {
site.blit_aabr(aabr, Tile {
kind: TileKind::Empty,
plot: Some(plot),
hard_alt: None,
});
site
@ -353,7 +360,7 @@ impl Site {
site.make_plaza(land, &mut rng);
let build_chance = Lottery::from(vec![(64.0, 1), (5.0, 2), (8.0, 3), (5.0, 4)]);
let build_chance = Lottery::from(vec![(64.0, 1), (5.0, 2), (8.0, 3), (5.0, 4), (5.0, 5)]);
let mut castles = 0;
@ -369,15 +376,17 @@ impl Site {
Extent2::broadcast(size),
)
}) {
let house = plot::House::generate(
land,
&mut reseed(&mut rng),
&site,
door_tile,
door_dir,
aabr,
);
let house_alt = house.alt;
let plot = site.create_plot(Plot {
kind: PlotKind::House(plot::House::generate(
land,
&mut reseed(&mut rng),
&site,
door_tile,
door_dir,
aabr,
)),
kind: PlotKind::House(house),
root_tile: aabr.center(),
tiles: aabr_tiles(aabr).collect(),
seed: rng.gen(),
@ -386,6 +395,42 @@ impl Site {
site.blit_aabr(aabr, Tile {
kind: TileKind::Building,
plot: Some(plot),
hard_alt: Some(house_alt),
});
} else {
site.make_plaza(land, &mut rng);
}
},
// Workshop
5 => {
let size = (2.0 + rng.gen::<f32>().powf(5.0) * 1.5).round() as u32;
if let Some((aabr, door_tile, door_dir)) = attempt(32, || {
site.find_roadside_aabr(
&mut rng,
4..(size + 1).pow(2),
Extent2::broadcast(size),
)
}) {
let workshop = plot::Workshop::generate(
land,
&mut reseed(&mut rng),
&site,
door_tile,
door_dir,
aabr,
);
let workshop_alt = workshop.alt;
let plot = site.create_plot(Plot {
kind: PlotKind::Workshop(workshop),
root_tile: aabr.center(),
tiles: aabr_tiles(aabr).collect(),
seed: rng.gen(),
});
site.blit_aabr(aabr, Tile {
kind: TileKind::Building,
plot: Some(plot),
hard_alt: Some(workshop_alt),
});
} else {
site.make_plaza(land, &mut rng);
@ -396,24 +441,23 @@ impl Site {
if let Some((_aabr, _, _door_dir)) = attempt(10, || {
site.find_roadside_aabr(&mut rng, 4..4, Extent2::new(2, 2))
}) {
/*
let plot = site.create_plot(Plot {
kind: PlotKind::Castle(plot::Castle::generate(
land,
&mut rng,
&site,
aabr,
)),
root_tile: aabr.center(),
tiles: aabr_tiles(aabr).collect(),
seed: rng.gen(),
});
// let plot = site.create_plot(Plot {
// kind: PlotKind::Castle(plot::Castle::generate(
// land,
// &mut rng,
// &site,
// aabr,
// )),
// root_tile: aabr.center(),
// tiles: aabr_tiles(aabr).collect(),
// seed: rng.gen(),
// });
site.blit_aabr(aabr, Tile {
kind: TileKind::Castle,
plot: Some(plot),
});
*/
// site.blit_aabr(aabr, Tile {
// kind: TileKind::Castle,
// plot: Some(plot),
// hard_alt: None,
// });
}
},
// Field
@ -444,6 +488,7 @@ impl Site {
site.tiles.set(tile, Tile {
kind: TileKind::Field,
plot: None,
hard_alt: None,
});
}
});
@ -451,7 +496,7 @@ impl Site {
*/
// Castle
4 if castles < 1 => {
if let Some((aabr, _entrance_tile, _door_dir)) = attempt(10, || {
if let Some((aabr, _entrance_tile, _door_dir)) = attempt(32, || {
site.find_roadside_aabr(&mut rng, 16 * 16..18 * 18, Extent2::new(16, 16))
}) {
let offset = rng.gen_range(5..(aabr.size().w.min(aabr.size().h) - 4));
@ -459,10 +504,10 @@ impl Site {
min: Vec2::new(aabr.min.x + offset - 1, aabr.min.y),
max: Vec2::new(aabr.min.x + offset + 2, aabr.min.y + 1),
};
let castle = plot::Castle::generate(land, &mut rng, &site, aabr, gate_aabr);
let castle_alt = castle.alt;
let plot = site.create_plot(Plot {
kind: PlotKind::Castle(plot::Castle::generate(
land, &mut rng, &site, aabr, gate_aabr,
)),
kind: PlotKind::Castle(castle),
root_tile: aabr.center(),
tiles: aabr_tiles(aabr).collect(),
seed: rng.gen(),
@ -471,11 +516,13 @@ impl Site {
let wall_north = Tile {
kind: TileKind::Wall(Ori::North),
plot: Some(plot),
hard_alt: Some(castle_alt),
};
let wall_east = Tile {
kind: TileKind::Wall(Ori::East),
plot: Some(plot),
hard_alt: Some(castle_alt),
};
for x in 0..aabr.size().w {
site.tiles
@ -497,14 +544,17 @@ impl Site {
let gate = Tile {
kind: TileKind::Gate,
plot: Some(plot),
hard_alt: Some(castle_alt),
};
let tower_parapet = Tile {
kind: TileKind::Tower(RoofKind::Parapet),
plot: Some(plot),
hard_alt: Some(castle_alt),
};
let tower_pyramid = Tile {
kind: TileKind::Tower(RoofKind::Pyramid),
plot: Some(plot),
hard_alt: Some(castle_alt),
};
site.tiles.set(
@ -542,6 +592,7 @@ impl Site {
Tile {
kind: TileKind::Road { a: 0, b: 0, w: 0 },
plot: Some(plot),
hard_alt: Some(castle_alt),
},
);
@ -554,6 +605,7 @@ impl Site {
Tile {
kind: TileKind::Wall(Ori::North),
plot: Some(plot),
hard_alt: Some(castle_alt),
},
);
site.tiles.set(
@ -581,6 +633,7 @@ impl Site {
Tile {
kind: TileKind::Keep(KeepKind::Middle),
plot: Some(plot),
hard_alt: Some(castle_alt),
},
);
@ -668,7 +721,7 @@ impl Site {
canvas.foreach_col(|canvas, wpos2d, col| {
let tpos = self.wpos_tile_pos(wpos2d);
let near_roads = SQUARE_9
let near_roads = CARDINALS
.iter()
.filter_map(|rpos| {
let tile = self.tiles.get(tpos + rpos);
@ -677,7 +730,7 @@ impl Site {
Some((LineSegment2 {
start: self.tile_wpos(path.nodes()[*a as usize]).map(|e| e as f32),
end: self.tile_wpos(path.nodes()[*b as usize]).map(|e| e as f32),
}, *w))
}, *w, tile.hard_alt))
} else {
None
}
@ -687,16 +740,32 @@ impl Site {
});
let wpos2df = wpos2d.map(|e| e as f32);
let dist = near_roads
.map(|(line, w)| (line.distance_to_point(wpos2df) - w as f32 * 2.0).max(0.0))
.min_by_key(|d| (*d * 100.0) as i32);
let mut min_dist = None;
let mut avg_hard_alt = None;
for (line, w, hard_alt) in near_roads {
let dist = line.distance_to_point(wpos2df);
let path_width = w as f32 * 2.0;
if dist < path_width {
min_dist = Some(min_dist.map(|d: f32| d.min(dist)).unwrap_or(dist));
if dist.map_or(false, |d| d <= 0.75) {
let alt = canvas.col(wpos2d).map_or(0, |col| col.alt as i32);
if let Some(ha) = hard_alt {
let w = path_width - dist;
let (sum, weight) = avg_hard_alt.unwrap_or((0.0, 0.0));
avg_hard_alt = Some((sum + ha as f32 * w, weight + w));
}
}
}
// let dist = near_roads
// .map(|(line, w)| (line.distance_to_point(wpos2df) - w as f32 * 2.0).max(0.0))
// .min_by_key(|d| (*d * 100.0) as i32);
if min_dist.is_some() {
let alt = /*avg_hard_alt.map(|(sum, weight)| sum / weight).unwrap_or_else(||*/ canvas.col(wpos2d).map_or(0.0, |col| col.alt)/*)*/ as i32;
(-6..4).for_each(|z| canvas.map(
Vec3::new(wpos2d.x, wpos2d.y, alt + z),
|b| if z >= 0 {
let sprite = if z == 0 && self.tile_wpos(tpos) == wpos2d && tpos.sum() % 2 == 0 {
|b| if z > 0 {
let sprite = if z == 1 && self.tile_wpos(tpos) == wpos2d && (tpos + tpos.yx() / 2) % 2 == Vec2::zero() {
SpriteKind::StreetLamp
} else {
SpriteKind::Empty
@ -786,9 +855,12 @@ impl Site {
max: wpos2d + TerrainChunkSize::RECT_SIZE.as_::<i32>(),
};
let info = canvas.info();
for plot in plots_to_render {
let (prim_tree, fills) = match &self.plots[plot].kind {
PlotKind::House(house) => house.render_collect(self, &canvas.land()),
PlotKind::Workshop(workshop) => workshop.render_collect(self, &canvas.land()),
PlotKind::Castle(castle) => castle.render_collect(self, &canvas.land()),
PlotKind::Dungeon(dungeon) => dungeon.render_collect(self, &canvas.land()),
_ => continue,
@ -816,10 +888,10 @@ 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, &canvas.info)
{
canvas.set(pos, block);
}
canvas.map(pos, |block| {
fill.sample_at(&prim_tree, prim, pos, &info, block)
.unwrap_or(block)
});
}
}
}

View File

@ -1,8 +1,9 @@
mod castle;
pub mod dungeon;
mod house;
mod workshop;
pub use self::{castle::Castle, dungeon::Dungeon, house::House};
pub use self::{castle::Castle, dungeon::Dungeon, house::House, workshop::Workshop};
use super::*;
use crate::util::DHashSet;
@ -39,6 +40,7 @@ impl Plot {
pub enum PlotKind {
House(House),
Workshop(Workshop),
Plaza,
Castle(Castle),
Road(Path<Vec2<i32>>),

View File

@ -10,7 +10,7 @@ pub struct Castle {
_bounds: Aabr<i32>,
gate_aabr: Aabr<i32>,
gate_alt: i32,
alt: i32,
pub(crate) alt: i32,
}
impl Castle {
@ -43,13 +43,7 @@ impl Castle {
impl Structure for Castle {
#[allow(clippy::identity_op)]
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Id<Primitive>, Fill)>(
&self,
site: &Site,
_land: &Land,
mut prim: F,
mut fill: G,
) {
fn render(&self, site: &Site, _land: &Land, painter: &Painter) {
let wall_height = 24;
let parapet_height = 2;
let parapet_gap = 2;
@ -61,8 +55,8 @@ impl Structure for Castle {
let _keep_height = wall_height + keep_levels * keep_level_height + 1;
let wall_rgb = Rgb::new(38, 46, 43);
// Flatten inside of the castle
fill(
prim(Primitive::Aabb(Aabb {
painter.fill(
painter.prim(Primitive::Aabb(Aabb {
min: site.tile_wpos(self.tile_aabr.min).with_z(self.gate_alt),
max: site
.tile_wpos(self.tile_aabr.max)
@ -70,8 +64,8 @@ impl Structure for Castle {
})),
Fill::Block(Block::empty()),
);
fill(
prim(Primitive::Aabb(Aabb {
painter.fill(
painter.prim(Primitive::Aabb(Aabb {
min: site.tile_wpos(self.tile_aabr.min).with_z(self.gate_alt),
max: site.tile_wpos(self.tile_aabr.max).with_z(self.gate_alt + 1),
})),
@ -84,22 +78,22 @@ impl Structure for Castle {
match site.tiles.get(tile_pos).kind.clone() {
TileKind::Wall(ori) => {
let dir = ori.dir();
let wall = prim(Primitive::Aabb(Aabb {
let wall = painter.prim(Primitive::Aabb(Aabb {
min: wpos.with_z(self.alt - 20),
max: (wpos + ts).with_z(self.alt + wall_height),
}));
// TODO Figure out logic to choose on on which site wall should be placed
// (inner, outer)
let parapet = prim(Primitive::Aabb(Aabb {
let parapet = painter.prim(Primitive::Aabb(Aabb {
min: (wpos - dir.yx()).with_z(self.alt + wall_height),
max: (wpos + ts * dir).with_z(self.alt + wall_height + parapet_height),
}));
let parapet2 = prim(Primitive::Aabb(Aabb {
let parapet2 = painter.prim(Primitive::Aabb(Aabb {
min: (wpos + ts * dir.yx()).with_z(self.alt + wall_height),
max: (wpos + (ts + 1) * dir.yx() + ts * dir)
.with_z(self.alt + wall_height + parapet_height),
}));
let cut_sides = prim(Primitive::Aabb(Aabb {
let cut_sides = painter.prim(Primitive::Aabb(Aabb {
min: (wpos + parapet_offset * dir - dir.yx())
.with_z(self.alt + wall_height + parapet_height - 1),
max: (wpos
@ -108,12 +102,12 @@ impl Structure for Castle {
.with_z(self.alt + wall_height + parapet_height),
}));
fill(wall, Fill::Brick(BlockKind::Rock, wall_rgb, 12));
let sides = prim(Primitive::Or(parapet, parapet2));
fill(sides, Fill::Brick(BlockKind::Rock, wall_rgb, 12));
painter.fill(wall, Fill::Brick(BlockKind::Rock, wall_rgb, 12));
let sides = painter.prim(Primitive::or(parapet, parapet2));
painter.fill(sides, Fill::Brick(BlockKind::Rock, wall_rgb, 12));
if (x + y).is_odd() {
fill(
prim(Primitive::Aabb(Aabb {
painter.fill(
painter.prim(Primitive::Aabb(Aabb {
min: (wpos + 2 * dir - dir.yx()).with_z(self.alt - 20),
max: (wpos + 4 * dir + (ts + 1) * dir.yx())
.with_z(self.alt + wall_height),
@ -121,55 +115,55 @@ impl Structure for Castle {
Fill::Brick(BlockKind::Rock, wall_rgb, 12),
);
} else {
let window_top = prim(Primitive::Aabb(Aabb {
let window_top = painter.prim(Primitive::Aabb(Aabb {
min: (wpos + 2 * dir).with_z(self.alt + wall_height / 4 + 9),
max: (wpos + (ts - 2) * dir + dir.yx())
.with_z(self.alt + wall_height / 4 + 12),
}));
let window_bottom = prim(Primitive::Aabb(Aabb {
let window_bottom = painter.prim(Primitive::Aabb(Aabb {
min: (wpos + 1 * dir).with_z(self.alt + wall_height / 4),
max: (wpos + (ts - 1) * dir + dir.yx())
.with_z(self.alt + wall_height / 4 + 9),
}));
let window_top2 = prim(Primitive::Aabb(Aabb {
let window_top2 = painter.prim(Primitive::Aabb(Aabb {
min: (wpos + 2 * dir + (ts - 1) * dir.yx())
.with_z(self.alt + wall_height / 4 + 9),
max: (wpos + (ts - 2) * dir + ts * dir.yx())
.with_z(self.alt + wall_height / 4 + 12),
}));
let window_bottom2 = prim(Primitive::Aabb(Aabb {
let window_bottom2 = painter.prim(Primitive::Aabb(Aabb {
min: (wpos + 1 * dir + (ts - 1) * dir.yx())
.with_z(self.alt + wall_height / 4),
max: (wpos + (ts - 1) * dir + ts * dir.yx())
.with_z(self.alt + wall_height / 4 + 9),
}));
fill(window_bottom, Fill::Block(Block::empty()));
fill(window_top, Fill::Block(Block::empty()));
fill(window_bottom2, Fill::Block(Block::empty()));
fill(window_top2, Fill::Block(Block::empty()));
painter.fill(window_bottom, Fill::Block(Block::empty()));
painter.fill(window_top, Fill::Block(Block::empty()));
painter.fill(window_bottom2, Fill::Block(Block::empty()));
painter.fill(window_top2, Fill::Block(Block::empty()));
}
fill(cut_sides, Fill::Block(Block::empty()));
painter.fill(cut_sides, Fill::Block(Block::empty()));
},
TileKind::Tower(roof) => {
let tower_total_height =
self.alt + wall_height + parapet_height + tower_height;
let tower_lower = prim(Primitive::Aabb(Aabb {
let tower_lower = painter.prim(Primitive::Aabb(Aabb {
min: (wpos - 1).with_z(self.alt - 20),
max: (wpos + ts + 1).with_z(tower_total_height),
}));
fill(tower_lower, Fill::Brick(BlockKind::Rock, wall_rgb, 12));
let tower_upper = prim(Primitive::Aabb(Aabb {
painter.fill(tower_lower, Fill::Brick(BlockKind::Rock, wall_rgb, 12));
let tower_upper = painter.prim(Primitive::Aabb(Aabb {
min: (wpos - 2).with_z(tower_total_height - 4i32),
max: (wpos + ts + 2).with_z(tower_total_height - 2i32),
}));
let tower_upper2 = prim(Primitive::Aabb(Aabb {
let tower_upper2 = painter.prim(Primitive::Aabb(Aabb {
min: (wpos - 3).with_z(tower_total_height - 2i32),
max: (wpos + ts + 3).with_z(tower_total_height),
}));
fill(
prim(Primitive::Or(tower_upper, tower_upper2)),
painter.fill(
painter.prim(Primitive::or(tower_upper, tower_upper2)),
Fill::Brick(BlockKind::Rock, wall_rgb, 12),
);
@ -178,8 +172,8 @@ impl Structure for Castle {
let roof_lip = 1;
let roof_height = (ts + 3) / 2 + roof_lip + 1;
fill(
prim(Primitive::Pyramid {
painter.fill(
painter.prim(Primitive::Pyramid {
aabb: Aabb {
min: (wpos - 3 - roof_lip).with_z(tower_total_height),
max: (wpos + ts + 3 + roof_lip)
@ -191,27 +185,27 @@ impl Structure for Castle {
);
},
RoofKind::Parapet => {
let tower_top_outer = prim(Primitive::Aabb(Aabb {
let tower_top_outer = painter.prim(Primitive::Aabb(Aabb {
min: (wpos - 3).with_z(
self.alt + wall_height + parapet_height + tower_height,
),
max: (wpos + ts + 3)
.with_z(tower_total_height + parapet_height),
}));
let tower_top_inner = prim(Primitive::Aabb(Aabb {
let tower_top_inner = painter.prim(Primitive::Aabb(Aabb {
min: (wpos - 2).with_z(tower_total_height),
max: (wpos + ts + 2)
.with_z(tower_total_height + parapet_height),
}));
fill(
prim(Primitive::Xor(tower_top_outer, tower_top_inner)),
painter.fill(
painter.prim(Primitive::xor(tower_top_outer, tower_top_inner)),
Fill::Brick(BlockKind::Rock, wall_rgb, 12),
);
for x in (wpos.x..wpos.x + ts).step_by(2 * parapet_gap as usize) {
fill(
prim(Primitive::Aabb(Aabb {
painter.fill(
painter.prim(Primitive::Aabb(Aabb {
min: Vec3::new(x, wpos.y - 3, tower_total_height + 1),
max: Vec3::new(
x + parapet_gap,
@ -223,8 +217,8 @@ impl Structure for Castle {
);
}
for y in (wpos.y..wpos.y + ts).step_by(2 * parapet_gap as usize) {
fill(
prim(Primitive::Aabb(Aabb {
painter.fill(
painter.prim(Primitive::Aabb(Aabb {
min: Vec3::new(wpos.x - 3, y, tower_total_height + 1),
max: Vec3::new(
wpos.x + ts + 3,
@ -239,16 +233,16 @@ impl Structure for Castle {
for &cpos in SQUARE_4.iter() {
let pos = wpos - 3 + (ts + 6) * cpos - cpos;
let pos2 = wpos - 2 + (ts + 4) * cpos - cpos;
fill(
prim(Primitive::Aabb(Aabb {
painter.fill(
painter.prim(Primitive::Aabb(Aabb {
min: pos.with_z(tower_total_height - 2),
max: (pos + 1)
.with_z(tower_total_height + parapet_height),
})),
Fill::Block(Block::empty()),
);
fill(
prim(Primitive::Aabb(Aabb {
painter.fill(
painter.prim(Primitive::Aabb(Aabb {
min: pos2.with_z(tower_total_height - 4),
max: (pos2 + 1).with_z(tower_total_height - 2),
})),
@ -263,8 +257,8 @@ impl Structure for Castle {
KeepKind::Middle => {
for i in 0..keep_levels + 1 {
let height = keep_level_height * i;
fill(
prim(Primitive::Aabb(Aabb {
painter.fill(
painter.prim(Primitive::Aabb(Aabb {
min: wpos.with_z(self.alt + height),
max: (wpos + ts).with_z(self.alt + height + 1),
})),
@ -297,12 +291,12 @@ impl Structure for Castle {
max: (site.tile_wpos(self.gate_aabr.max) - Vec2::unit_x())
.with_z(self.alt + wall_height),
};
fill(
prim(Primitive::Aabb(gate_aabb)),
painter.fill(
painter.prim(Primitive::Aabb(gate_aabb)),
Fill::Brick(BlockKind::Rock, wall_rgb, 12),
);
fill(
prim(Primitive::Aabb(Aabb {
painter.fill(
painter.prim(Primitive::Aabb(Aabb {
min: (gate_aabb.min + Vec3::unit_x() * 2 + Vec3::unit_z() * 2),
max: (gate_aabb.max - Vec3::unit_x() * 2 - Vec3::unit_z() * 16),
})),
@ -310,8 +304,8 @@ impl Structure for Castle {
);
let height = self.alt + wall_height - 17;
for i in 1..5 {
fill(
prim(Primitive::Aabb(Aabb {
painter.fill(
painter.prim(Primitive::Aabb(Aabb {
min: Vec3::new(gate_aabb.min.x + 2 + i, gate_aabb.min.y, height + i as i32),
max: Vec3::new(
gate_aabb.max.x - 2 - i,
@ -324,8 +318,8 @@ impl Structure for Castle {
}
let height = self.alt + wall_height - 7;
for x in (gate_aabb.min.x + 1..gate_aabb.max.x - 2).step_by(4) {
fill(
prim(Primitive::Aabb(Aabb {
painter.fill(
painter.prim(Primitive::Aabb(Aabb {
min: Vec3::new(x, gate_aabb.min.y + 1, height - 13),
max: Vec3::new(x + 2, gate_aabb.min.y + 2, height),
})),
@ -333,8 +327,8 @@ impl Structure for Castle {
);
}
for z in (height - 12..height).step_by(4) {
fill(
prim(Primitive::Aabb(Aabb {
painter.fill(
painter.prim(Primitive::Aabb(Aabb {
min: Vec3::new(gate_aabb.min.x + 2, gate_aabb.min.y + 1, z),
max: Vec3::new(gate_aabb.max.x - 2, gate_aabb.min.y + 2, z + 2),
})),

View File

@ -1,4 +1,4 @@
use super::SpawnRules;
use super::*;
use crate::{
site::namegen::NameGen,
site2::{self, aabr_with_z, Fill, Primitive, Structure as SiteStructure},
@ -1021,13 +1021,7 @@ pub fn make_wall_contours(
}
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,
) {
fn render(&self, painter: &Painter, 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 {
@ -1035,7 +1029,7 @@ impl Floor {
max: (floor_corner + TILE_SIZE * self.tiles.size())
.with_z(floor_z + self.total_depth()),
};
let floor_prim = prim(Primitive::Aabb(floor_aabb));
let floor_prim = painter.prim(Primitive::Aabb(floor_aabb));
// This is copied from `src/layer/mod.rs`. It should be moved into
// a util file somewhere
@ -1060,7 +1054,7 @@ impl Floor {
// 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(
let floor_sprite = painter.prim(Primitive::sampling(
floor_prim,
Box::new(|pos| RandomField::new(7331).chance(pos, 0.001)),
));
@ -1090,13 +1084,13 @@ impl Floor {
// 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 wall_contours = painter.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 wall_contour_surface = painter.prim(Primitive::sampling(floor_prim, {
let tiles = Arc::clone(&tiles);
make_wall_contours(
tiles,
@ -1135,27 +1129,27 @@ impl Floor {
// 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 mut lighting_mask_x = painter.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 {
let plane = painter.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));
lighting_mask_x = painter.prim(Primitive::or(plane, lighting_mask_x));
}
let mut lighting_mask_y = prim(Primitive::Empty);
let mut lighting_mask_y = painter.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 {
let plane = painter.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));
lighting_mask_y = painter.prim(Primitive::or(plane, lighting_mask_y));
}
prim(Primitive::Xor(lighting_mask_x, lighting_mask_y))
painter.prim(Primitive::xor(lighting_mask_x, lighting_mask_y))
};
// Declare collections of various disjoint primitives that need postprocessing
@ -1184,18 +1178,18 @@ impl Floor {
};
// Sprites are contained to the level above the floor, and not within walls
let sprite_layer = prim(Primitive::Aabb(aabr_with_z(
let sprite_layer = painter.prim(Primitive::Aabb(aabr_with_z(
tile_aabr,
floor_z..floor_z + 1,
)));
let sprite_layer = prim(Primitive::Diff(sprite_layer, wall_contours));
let sprite_layer = painter.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(
let lighting_plane = painter.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 lighting_plane = painter.prim(Primitive::and(lighting_plane, lighting_mask));
let mut chests = None;
@ -1208,33 +1202,33 @@ impl Floor {
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 {
let bb = painter.prim(match kind {
StairsKind::Spiral => Primitive::Cylinder(aabb),
StairsKind::WallSpiral => Primitive::Aabb(aabb),
});
let stair = prim(Primitive::Sampling(bb, match kind {
let stair = painter.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);
let mut lights = painter.prim(Primitive::Empty);
for i in height..self.total_depth() {
if i % 9 == 0 {
let mut light = prim(Primitive::Aabb(Aabb {
let mut light = painter.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 {
let inner = painter.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));
light = painter.prim(Primitive::diff(light, inner));
lights = painter.prim(Primitive::or(light, lights));
}
}
lights = prim(Primitive::And(lights, lighting_mask));
lights = painter.prim(Primitive::and(lights, lighting_mask));
stairs_bb.push(bb);
stairs.push((stair, lights));
}
@ -1245,7 +1239,7 @@ impl Floor {
// 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(
let chest_sprite = painter.prim(Primitive::sampling(
sprite_layer,
Box::new(move |pos| RandomField::new(seed).chance(pos, loot_density * 0.5)),
));
@ -1263,22 +1257,22 @@ impl Floor {
// If a room has pits, place them
if room.pits.is_some() {
// Make an air pit
let tile_pit = prim(Primitive::Aabb(aabr_with_z(
let tile_pit = painter.prim(Primitive::Aabb(aabr_with_z(
tile_aabr,
floor_z - 7..floor_z,
)));
let tile_pit = prim(Primitive::Diff(tile_pit, wall_contours));
fill(tile_pit, Fill::Block(vacant));
let tile_pit = painter.prim(Primitive::diff(tile_pit, wall_contours));
painter.fill(tile_pit, Fill::Block(vacant));
// Fill with lava
let tile_lava = prim(Primitive::Aabb(aabr_with_z(
let tile_lava = painter.prim(Primitive::Aabb(aabr_with_z(
tile_aabr,
floor_z - 7..floor_z - 5,
)));
let tile_lava = prim(Primitive::Diff(tile_lava, wall_contours));
let tile_lava = painter.prim(Primitive::diff(tile_lava, wall_contours));
//pits.push(tile_pit);
//pits.push(tile_lava);
fill(tile_lava, Fill::Block(lava));
painter.fill(tile_lava, Fill::Block(lava));
}
if room
.pits
@ -1287,12 +1281,12 @@ impl Floor {
})
.unwrap_or(false)
{
let platform = prim(Primitive::Aabb(Aabb {
let platform = painter.prim(Primitive::Aabb(Aabb {
min: (tile_center - Vec2::broadcast(pillar_thickness - 1))
.with_z(floor_z - 7),
max: (tile_center + Vec2::broadcast(pillar_thickness)).with_z(floor_z),
}));
fill(platform, Fill::Block(stone));
painter.fill(platform, Fill::Block(stone));
}
// If a room has pillars, the current tile aligns with the pillar spacing, and
@ -1313,13 +1307,13 @@ impl Floor {
matches!(self.tiles.get(other_tile_pos), Some(Tile::Room(_)))
})
{
let mut pillar = prim(Primitive::Cylinder(Aabb {
let mut pillar = painter.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 {
let base = painter.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))
@ -1327,14 +1321,14 @@ impl Floor {
}));
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));
let mut lights = painter
.prim(Primitive::scale(pillar, Vec2::broadcast(scale).with_z(1.0)));
lights = painter.prim(Primitive::and(lighting_plane, lights));
// Only add the base (and shift the lights up)
// for boss-rooms pillars
if room.kind == RoomKind::Boss {
lights = prim(Primitive::Translate(lights, 3 * Vec3::unit_z()));
pillar = prim(Primitive::Or(pillar, base));
lights = painter.prim(Primitive::translate(lights, 3 * Vec3::unit_z()));
pillar = painter.prim(Primitive::or(pillar, base));
}
pillars.push((tile_center, pillar, lights));
}
@ -1348,39 +1342,39 @@ impl Floor {
}
// Carve out the room's air inside the walls
let tile_air = prim(Primitive::Aabb(aabr_with_z(
let tile_air = painter.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));
let tile_air = painter.prim(Primitive::diff(tile_air, wall_contours));
painter.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());
let sconces_layer = painter.prim(Primitive::and(tile_air, lighting_plane));
let sconces_layer = painter.prim(Primitive::and(sconces_layer, wall_contour_surface));
painter.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));
let chest_sprite = painter.prim(Primitive::diff(chest_sprite, wall_contours));
sprites.push((chest_sprite, chest_sprite_fill));
}
let floor_sprite = prim(Primitive::And(sprite_layer, floor_sprite));
let floor_sprite = painter.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 {
let magic_circle_bb = painter.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(
let magic_circle = painter.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));
painter.fill(magic_circle, Fill::Block(stone_purple));
}
// Place pillars and pillar lights facing the pillars
@ -1391,38 +1385,32 @@ impl Floor {
continue;
}
}
fill(*lights, sconces_inward.clone());
fill(*pillar, Fill::Block(stone));
painter.fill(*lights, sconces_inward.clone());
painter.fill(*pillar, Fill::Block(stone));
}
// Carve out space for the stairs
for stair_bb in stairs_bb.iter() {
fill(*stair_bb, Fill::Block(vacant));
painter.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()));
let stair_bb_up = painter.prim(Primitive::translate(*stair_bb, Vec3::unit_z()));
for (sprite, _) in sprites.iter_mut() {
*sprite = prim(Primitive::Diff(*sprite, stair_bb_up));
*sprite = painter.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));
painter.fill(*lights, sconces_outward.clone());
painter.fill(*stair, Fill::Block(stone));
}
// Place the sprites
for (sprite, sprite_fill) in sprites.into_iter() {
fill(sprite, sprite_fill);
painter.fill(sprite, sprite_fill);
}
}
}
impl SiteStructure for Dungeon {
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Id<Primitive>, Fill)>(
&self,
_site: &site2::Site,
land: &Land,
mut prim: F,
mut fill: G,
) {
fn render(&self, _site: &site2::Site, land: &Land, painter: &Painter) {
let origin = (self.origin + Vec2::broadcast(TILE_SIZE / 2)).with_z(self.alt + ALT_OFFSET);
lazy_static! {
@ -1445,9 +1433,9 @@ impl SiteStructure for Dungeon {
let entrances = entrances.read();
let entrance = entrances[self.seed as usize % entrances.len()].clone();
let entrance_prim = prim(Primitive::Prefab(Box::new(entrance.clone())));
let entrance_prim = prim(Primitive::Translate(entrance_prim, origin));
fill(
let entrance_prim = painter.prim(Primitive::Prefab(Box::new(entrance.clone())));
let entrance_prim = painter.prim(Primitive::translate(entrance_prim, origin));
painter.fill(
entrance_prim,
Fill::Prefab(Box::new(entrance), origin, self.seed),
);
@ -1456,7 +1444,7 @@ impl SiteStructure for Dungeon {
for floor in &self.floors {
z -= floor.total_depth();
floor.render(&mut prim, &mut fill, self, z);
floor.render(painter, self, z);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,120 @@
use super::*;
use crate::Land;
use common::terrain::{Block, BlockKind, SpriteKind};
use rand::prelude::*;
use vek::*;
/// Represents house data generated by the `generate()` method
pub struct Workshop {
/// Tile position of the door tile
door_tile: Vec2<i32>,
/// Axis aligned bounding region of tiles
tile_aabr: Aabr<i32>,
/// Axis aligned bounding region for the house
bounds: Aabr<i32>,
/// Approximate altitude of the door tile
pub(crate) alt: i32,
}
impl Workshop {
pub fn generate(
land: &Land,
rng: &mut impl Rng,
site: &Site,
door_tile: Vec2<i32>,
door_dir: Vec2<i32>,
tile_aabr: Aabr<i32>,
) -> Self {
let levels = rng.gen_range(1..2 + (tile_aabr.max - tile_aabr.min).product() / 6) as u32;
let door_tile_pos = site.tile_center_wpos(door_tile);
let bounds = Aabr {
min: site.tile_wpos(tile_aabr.min),
max: site.tile_wpos(tile_aabr.max),
};
Self {
door_tile: door_tile_pos,
tile_aabr,
bounds,
alt: land.get_alt_approx(site.tile_center_wpos(door_tile + door_dir)) as i32,
}
}
}
impl Structure for Workshop {
fn render(&self, site: &Site, _land: &Land, painter: &Painter) {
let brick = Fill::Brick(BlockKind::Rock, Rgb::new(80, 75, 85), 24);
let base = self.alt + 1;
let center = (self.bounds.min + self.bounds.max) / 2;
// Base
painter
.aabb(Aabb {
min: self.bounds.min.with_z(base - 16),
max: self.bounds.max.with_z(base),
})
.fill(brick.clone());
let roof = base + 5;
painter
.aabb(Aabb {
min: self.bounds.min.with_z(base),
max: self.bounds.max.with_z(roof),
})
.clear();
// Supports
for pos in [
Vec2::new(self.bounds.min.x + 2, self.bounds.min.y + 2),
Vec2::new(self.bounds.max.x - 3, self.bounds.min.y + 2),
Vec2::new(self.bounds.min.x + 2, self.bounds.max.y - 3),
Vec2::new(self.bounds.max.x - 3, self.bounds.max.y - 3),
] {
painter
.line(pos.with_z(base), pos.with_z(roof), 1.0)
.fill(Fill::Block(Block::new(
BlockKind::Wood,
Rgb::new(55, 25, 8),
)));
}
let roof_top = roof + 5;
// Roof
painter
.pyramid(Aabb {
min: (self.bounds.min + 1).with_z(roof),
max: (self.bounds.max - 1).with_z(roof_top),
})
.fill(Fill::Brick(BlockKind::Rock, Rgb::new(45, 28, 21), 24));
let chimney = roof_top + 2;
// Chimney
let chimney_radius = 3.0;
painter
.line(
center.with_z(base + 4),
center.with_z(chimney),
chimney_radius,
)
.fill(brick);
painter
.line(
center.with_z(base),
center.with_z(chimney + 2),
chimney_radius - 1.0,
)
.clear();
for x in -1..2 {
for y in -1..2 {
painter.sprite(
(center + Vec2::new(x, y)).with_z(base - 1),
SpriteKind::Ember,
);
}
}
}
}

View File

@ -192,6 +192,7 @@ pub enum TileKind {
pub struct Tile {
pub(crate) kind: TileKind,
pub(crate) plot: Option<Id<Plot>>,
pub(crate) hard_alt: Option<i32>,
}
impl Tile {
@ -199,11 +200,18 @@ impl Tile {
Self {
kind: TileKind::Empty,
plot: None,
hard_alt: None,
}
}
/// Create a tile that is not associated with any plot.
pub const fn free(kind: TileKind) -> Self { Self { kind, plot: None } }
pub const fn free(kind: TileKind) -> Self {
Self {
kind,
plot: None,
hard_alt: None,
}
}
pub fn is_empty(&self) -> bool { self.kind == TileKind::Empty }