Better house roofs

This commit is contained in:
Joshua Barretto 2021-05-02 20:32:52 +01:00
parent 09c0ea0f3e
commit e4ab0be63d
7 changed files with 129 additions and 49 deletions

View File

@ -29,7 +29,7 @@ opt-level = 2
overflow-checks = true
debug-assertions = true
panic = "abort"
debug = false
debug = true
codegen-units = 8
lto = false
# TEMP false to avoid fingerprints bug

View File

@ -108,7 +108,7 @@ impl Civs {
attempt(5, || {
let (kind, size) = match ctx.rng.gen_range(0..64) {
0..=4 => (SiteKind::Castle, 3),
// 5..=28 => (SiteKind::Refactor, 6),
5..=28 => (SiteKind::Refactor, 6),
29..=31 => (SiteKind::Tree, 4),
_ => (SiteKind::Dungeon, 0),
};

View File

@ -10,9 +10,12 @@ use vek::*;
pub enum Primitive {
Empty, // Placeholder
Plot, // A primitive that fits the floor plan of the plot
Void, // A primitive that fits the floor plan of void tiles
// Shapes
Aabb(Aabb<i32>),
Pyramid { aabb: Aabb<i32>, inset: i32 },
Pyramid { aabb: Aabb<i32>, inset: Vec2<i32> },
Cylinder(Aabb<i32>),
Cone(Aabb<i32>),
Sphere(Aabb<i32>),
@ -20,21 +23,31 @@ pub enum Primitive {
// Combinators
And(Id<Primitive>, Id<Primitive>),
AndNot(Id<Primitive>, Id<Primitive>), // Not second
Or(Id<Primitive>, Id<Primitive>),
Xor(Id<Primitive>, Id<Primitive>),
// Not commutative
Diff(Id<Primitive>, Id<Primitive>),
// Operators
Rotate(Id<Primitive>, Mat3<i32>),
Offset(Id<Primitive>, Vec3<i32>),
}
#[derive(Copy, Clone)]
pub enum Fill {
Block(Block),
Brick(BlockKind, Rgb<u8>, u8),
}
impl Fill {
fn contains_at(&self, tree: &Store<Primitive>, prim: Id<Primitive>, pos: Vec3<i32>) -> bool {
fn contains_at(
&self,
tree: &Store<Primitive>,
prim: Id<Primitive>,
pos: Vec3<i32>,
is_plot: &impl Fn(Vec2<i32>) -> bool,
is_void: &impl Fn(Vec2<i32>) -> bool,
) -> bool {
// Custom closure because vek's impl of `contains_point` is inclusive :(
let aabb_contains = |aabb: Aabb<i32>, pos: Vec3<i32>| {
(aabb.min.x..aabb.max.x).contains(&pos.x)
@ -45,18 +58,21 @@ impl Fill {
match &tree[prim] {
Primitive::Empty => false,
Primitive::Plot => is_plot(pos.xy()),
Primitive::Void => is_void(pos.xy()),
Primitive::Aabb(aabb) => aabb_contains(*aabb, pos),
Primitive::Pyramid { aabb, inset } => {
let inset = (*inset).max(aabb.size().reduce_min());
let inset = inset.map2(Vec2::new(aabb.size().w, aabb.size().h), |i, sz| i.min(sz));
let inner = Aabr {
min: aabb.min.xy() - 1 + inset,
max: aabb.max.xy() - inset,
};
aabb_contains(*aabb, pos)
&& (inner.projected_point(pos.xy()) - pos.xy())
&& ((inner.projected_point(pos.xy()) - pos.xy())
.map(|e| e.abs())
.reduce_max() as f32
/ (inset as f32)
.map2(inset, |e, i| e as f32 / (i as f32).max(0.00001))
.reduce_partial_max() as f32)
< 1.0
- ((pos.z - aabb.min.z) as f32 + 0.5) / (aabb.max.z - aabb.min.z) as f32
},
@ -96,21 +112,38 @@ impl Fill {
.dot(*gradient) as i32)
},
Primitive::And(a, b) => {
self.contains_at(tree, *a, pos) && self.contains_at(tree, *b, pos)
self.contains_at(tree, *a, pos, is_plot, is_void)
&& self.contains_at(tree, *b, pos, is_plot, is_void)
},
Primitive::AndNot(a, b) => {
self.contains_at(tree, *a, pos, is_plot, is_void)
&& !self.contains_at(tree, *b, pos, is_plot, is_void)
},
Primitive::Or(a, b) => {
self.contains_at(tree, *a, pos) || self.contains_at(tree, *b, pos)
self.contains_at(tree, *a, pos, is_plot, is_void)
|| self.contains_at(tree, *b, pos, is_plot, is_void)
},
Primitive::Xor(a, b) => {
self.contains_at(tree, *a, pos) ^ self.contains_at(tree, *b, pos)
self.contains_at(tree, *a, pos, is_plot, is_void)
^ self.contains_at(tree, *b, pos, is_plot, is_void)
},
Primitive::Diff(a, b) => {
self.contains_at(tree, *a, pos) && !self.contains_at(tree, *b, pos)
self.contains_at(tree, *a, pos, is_plot, is_void)
&& !self.contains_at(tree, *b, pos, is_plot, is_void)
},
Primitive::Rotate(prim, mat) => {
let aabb = self.get_bounds(tree, *prim);
let diff = pos - (aabb.min + mat.cols.map(|x| x.reduce_min()));
self.contains_at(tree, *prim, aabb.min + mat.transposed() * diff)
self.contains_at(
tree,
*prim,
aabb.min + mat.transposed() * diff,
is_plot,
is_void,
)
},
Primitive::Offset(prim, offset) => {
self.contains_at(tree, *prim, pos - offset, is_plot, is_void)
},
}
}
@ -120,8 +153,10 @@ impl Fill {
tree: &Store<Primitive>,
prim: Id<Primitive>,
pos: Vec3<i32>,
is_plot: impl Fn(Vec2<i32>) -> bool,
is_void: impl Fn(Vec2<i32>) -> bool,
) -> Option<Block> {
if self.contains_at(tree, prim, pos) {
if self.contains_at(tree, prim, pos, &is_plot, &is_void) {
match self {
Fill::Block(block) => Some(*block),
Fill::Brick(bk, col, range) => Some(Block::new(
@ -146,7 +181,7 @@ impl Fill {
}
Some(match &tree[prim] {
Primitive::Empty => return None,
Primitive::Empty | Primitive::Plot | Primitive::Void => return None,
Primitive::Aabb(aabb) => *aabb,
Primitive::Pyramid { aabb, .. } => *aabb,
Primitive::Cylinder(aabb) => *aabb,
@ -174,6 +209,7 @@ impl Fill {
self.get_bounds_inner(tree, *b),
|a, b| a.intersection(b),
)?,
Primitive::AndNot(a, _) => self.get_bounds_inner(tree, *a)?,
Primitive::Or(a, b) | Primitive::Xor(a, b) => or_zip_with(
self.get_bounds_inner(tree, *a),
self.get_bounds_inner(tree, *b),
@ -189,6 +225,13 @@ impl Fill {
};
new_aabb.made_valid()
},
Primitive::Offset(prim, offset) => {
let aabb = self.get_bounds_inner(tree, *prim)?;
Aabb {
min: aabb.min - offset,
max: aabb.max - offset,
}
},
})
}

View File

@ -67,7 +67,7 @@ impl Site {
}
pub fn bounds(&self) -> Aabr<i32> {
let border = 1;
let border = 2;
Aabr {
min: self.origin + self.tile_wpos(self.tiles.bounds.min - border),
max: self.origin + self.tile_wpos(self.tiles.bounds.max + 1 + border),
@ -287,7 +287,7 @@ impl Site {
site.make_plaza(land, &mut rng);
let build_chance = Lottery::from(vec![(64.0, 1), (5.0, 2), (8.0, 3), (0.75, 4)]);
let build_chance = Lottery::from(vec![(128.0, 1), (5.0, 2), (8.0, 3), (0.75, 4)]);
let mut castles = 0;
@ -606,8 +606,8 @@ impl Site {
if let TileKind::Road { a, b, w } = &tile.kind {
if let Some(PlotKind::Road(path)) = tile.plot.map(|p| &self.plot(p).kind) {
Some((LineSegment2 {
start: self.tile_center_wpos(path.nodes()[*a as usize]).map(|e| e as f32),
end: self.tile_center_wpos(path.nodes()[*b as usize]).map(|e| e as f32),
start: self.tile_center_wpos(path.nodes()[*a as usize]).map(|e| e as f32) - TILE_SIZE as f32 / 2.0,
end: self.tile_center_wpos(path.nodes()[*b as usize]).map(|e| e as f32) - TILE_SIZE as f32 / 2.0,
}, *w))
} else {
None
@ -622,16 +622,19 @@ impl Site {
.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 dist.map_or(false, |d| d <= 0.75) {
let alt = canvas.col(wpos2d).map_or(0, |col| col.alt as i32);
if let Some(col) = canvas.col(wpos2d).filter(|_| dist.map_or(false, |d| d <= 0.75)) {
let surf = col.alt.max(col.water_level + 5.0) as i32;
let is_pier = col.water_dist.map_or(false, |d| d < 3.0);
(-6..4).for_each(|z| canvas.map(
Vec3::new(wpos2d.x, wpos2d.y, alt + z),
Vec3::new(wpos2d.x, wpos2d.y, surf + z),
|b| if z >= 0 {
if b.is_filled() {
Block::empty()
} else {
b.with_sprite(SpriteKind::Empty)
}
} else if is_pier {
Block::new(BlockKind::Wood, Rgb::new(110, 65, 15))
} else {
Block::new(BlockKind::Rock, Rgb::new(55, 45, 50))
},
@ -720,8 +723,12 @@ impl Site {
for y in aabb.min.y..aabb.max.y {
for z in aabb.min.z..aabb.max.z {
let pos = Vec3::new(x, y, z);
let is_plot = self.wpos_tile(pos.xy()).plot == Some(plot);
let is_void = self.wpos_tile(pos.xy()).is_void();
if let Some(block) = fill.sample_at(&prim_tree, prim, pos) {
if let Some(block) =
fill.sample_at(&prim_tree, prim, pos, |_| is_plot, |_| is_void)
{
canvas.set(pos, block);
}
}

View File

@ -184,7 +184,7 @@ impl Structure for Castle {
max: (wpos + ts + 3 + roof_lip)
.with_z(tower_total_height + roof_height),
},
inset: roof_height,
inset: Vec2::broadcast(roof_height),
}),
Fill::Brick(BlockKind::Wood, Rgb::new(40, 5, 11), 10),
);

View File

@ -11,6 +11,7 @@ pub struct House {
alt: i32,
levels: u32,
roof_color: Rgb<u8>,
roof_inset: Vec2<bool>,
}
impl House {
@ -42,6 +43,11 @@ impl House {
];
*colors.choose(rng).unwrap()
},
roof_inset: match rng.gen_range(0..3) {
0 => Vec2::new(true, false),
1 => Vec2::new(false, true),
_ => Vec2::new(true, true),
},
}
}
}
@ -56,6 +62,13 @@ impl Structure for House {
let storey = 5;
let roof = storey * self.levels as i32;
let foundations = 12;
let plot = prim(Primitive::Plot);
let void = prim(Primitive::Void);
let can_spill = prim(Primitive::Or(plot, void));
//let wall_block = Fill::Brick(BlockKind::Rock, Rgb::new(80, 75, 85), 24);
let wall_block = Fill::Brick(BlockKind::Rock, Rgb::new(158, 150, 121), 24);
let structural_wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(55, 25, 8)));
// Walls
let inner = prim(Primitive::Aabb(Aabb {
@ -66,10 +79,7 @@ impl Structure for House {
min: self.bounds.min.with_z(self.alt - foundations),
max: (self.bounds.max + 1).with_z(self.alt + roof),
}));
fill(
outer,
Fill::Brick(BlockKind::Rock, Rgb::new(80, 75, 85), 24),
);
fill(outer, wall_block);
fill(inner, Fill::Block(Block::empty()));
let walls = prim(Primitive::Xor(outer, inner));
@ -97,10 +107,7 @@ impl Structure for House {
pillars_x = prim(Primitive::Or(pillars_x, pillar));
}
let pillars = prim(Primitive::And(pillars_x, pillars_y));
fill(
pillars,
Fill::Block(Block::new(BlockKind::Wood, Rgb::new(55, 25, 8))),
);
fill(pillars, structural_wood);
// For each storey...
for i in 0..self.levels + 1 {
@ -147,13 +154,20 @@ impl Structure for House {
}
// Floor
let floor = prim(Primitive::Aabb(Aabb {
min: (self.bounds.min + 1).with_z(self.alt + height),
max: self.bounds.max.with_z(self.alt + height + 1),
}));
fill(
prim(Primitive::Aabb(Aabb {
min: (self.bounds.min + 1).with_z(self.alt + height),
max: self.bounds.max.with_z(self.alt + height + 1),
})),
floor,
Fill::Block(Block::new(BlockKind::Rock, Rgb::new(89, 44, 14))),
);
let slice = prim(Primitive::Aabb(Aabb {
min: self.bounds.min.with_z(self.alt + height),
max: (self.bounds.max + 1).with_z(self.alt + height + 1),
}));
fill(prim(Primitive::AndNot(slice, floor)), structural_wood);
}
let roof_lip = 2;
@ -165,23 +179,32 @@ impl Structure for House {
+ 1;
// Roof
let roof_vol = prim(Primitive::Pyramid {
aabb: Aabb {
min: (self.bounds.min - roof_lip).with_z(self.alt + roof),
max: (self.bounds.max + 1 + roof_lip).with_z(self.alt + roof + roof_height),
},
inset: self.roof_inset.map(|e| if e { roof_height } else { 0 }),
});
let eaves = prim(Primitive::Offset(roof_vol, -Vec3::unit_z()));
let tiles = prim(Primitive::AndNot(roof_vol, eaves));
fill(
prim(Primitive::Pyramid {
aabb: Aabb {
min: (self.bounds.min - roof_lip).with_z(self.alt + roof),
max: (self.bounds.max + 1 + roof_lip).with_z(self.alt + roof + roof_height),
},
inset: roof_height,
}),
prim(Primitive::And(tiles, can_spill)),
Fill::Block(Block::new(BlockKind::Wood, self.roof_color)),
);
let roof_inner = prim(Primitive::Aabb(Aabb {
min: self.bounds.min.with_z(self.alt + roof),
max: (self.bounds.max + 1).with_z(self.alt + roof + roof_height),
}));
fill(prim(Primitive::And(eaves, roof_inner)), wall_block);
// Foundations
let foundations = prim(Primitive::Aabb(Aabb {
min: (self.bounds.min - 1).with_z(self.alt - foundations),
max: (self.bounds.max + 2).with_z(self.alt + 1),
}));
fill(
prim(Primitive::Aabb(Aabb {
min: (self.bounds.min - 1).with_z(self.alt - foundations),
max: (self.bounds.max + 2).with_z(self.alt + 1),
})),
prim(Primitive::And(foundations, can_spill)),
Fill::Block(Block::new(BlockKind::Rock, Rgb::new(31, 33, 32))),
);
}

View File

@ -210,9 +210,16 @@ impl Tile {
pub fn is_road(&self) -> bool { matches!(self.kind, TileKind::Road { .. }) }
pub fn is_obstacle(&self) -> bool {
matches!(
match self.kind {
TileKind::Hazard(_) => true,
_ => !self.is_void(),
}
}
pub fn is_void(&self) -> bool {
!matches!(
self.kind,
TileKind::Hazard(_) | TileKind::Building | TileKind::Castle | TileKind::Wall(_)
TileKind::Building | TileKind::Castle | TileKind::Wall(_)
)
}
}