Better house roofs

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

View File

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

View File

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

View File

@ -10,9 +10,12 @@ use vek::*;
pub enum Primitive { pub enum Primitive {
Empty, // Placeholder 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 // Shapes
Aabb(Aabb<i32>), Aabb(Aabb<i32>),
Pyramid { aabb: Aabb<i32>, inset: i32 }, Pyramid { aabb: Aabb<i32>, inset: Vec2<i32> },
Cylinder(Aabb<i32>), Cylinder(Aabb<i32>),
Cone(Aabb<i32>), Cone(Aabb<i32>),
Sphere(Aabb<i32>), Sphere(Aabb<i32>),
@ -20,21 +23,31 @@ pub enum Primitive {
// Combinators // Combinators
And(Id<Primitive>, Id<Primitive>), And(Id<Primitive>, Id<Primitive>),
AndNot(Id<Primitive>, Id<Primitive>), // Not second
Or(Id<Primitive>, Id<Primitive>), Or(Id<Primitive>, Id<Primitive>),
Xor(Id<Primitive>, Id<Primitive>), Xor(Id<Primitive>, Id<Primitive>),
// Not commutative // Not commutative
Diff(Id<Primitive>, Id<Primitive>), Diff(Id<Primitive>, Id<Primitive>),
// Operators // Operators
Rotate(Id<Primitive>, Mat3<i32>), Rotate(Id<Primitive>, Mat3<i32>),
Offset(Id<Primitive>, Vec3<i32>),
} }
#[derive(Copy, Clone)]
pub enum Fill { pub enum Fill {
Block(Block), Block(Block),
Brick(BlockKind, Rgb<u8>, u8), Brick(BlockKind, Rgb<u8>, u8),
} }
impl Fill { 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 :( // Custom closure because vek's impl of `contains_point` is inclusive :(
let aabb_contains = |aabb: Aabb<i32>, pos: Vec3<i32>| { let aabb_contains = |aabb: Aabb<i32>, pos: Vec3<i32>| {
(aabb.min.x..aabb.max.x).contains(&pos.x) (aabb.min.x..aabb.max.x).contains(&pos.x)
@ -45,18 +58,21 @@ impl Fill {
match &tree[prim] { match &tree[prim] {
Primitive::Empty => false, Primitive::Empty => false,
Primitive::Plot => is_plot(pos.xy()),
Primitive::Void => is_void(pos.xy()),
Primitive::Aabb(aabb) => aabb_contains(*aabb, pos), Primitive::Aabb(aabb) => aabb_contains(*aabb, pos),
Primitive::Pyramid { aabb, inset } => { 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 { let inner = Aabr {
min: aabb.min.xy() - 1 + inset, min: aabb.min.xy() - 1 + inset,
max: aabb.max.xy() - inset, max: aabb.max.xy() - inset,
}; };
aabb_contains(*aabb, pos) aabb_contains(*aabb, pos)
&& (inner.projected_point(pos.xy()) - pos.xy()) && ((inner.projected_point(pos.xy()) - pos.xy())
.map(|e| e.abs()) .map(|e| e.abs())
.reduce_max() as f32 .map2(inset, |e, i| e as f32 / (i as f32).max(0.00001))
/ (inset as f32) .reduce_partial_max() as f32)
< 1.0 < 1.0
- ((pos.z - aabb.min.z) as f32 + 0.5) / (aabb.max.z - aabb.min.z) as f32 - ((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) .dot(*gradient) as i32)
}, },
Primitive::And(a, b) => { 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) => { 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) => { 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) => { 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) => { Primitive::Rotate(prim, mat) => {
let aabb = self.get_bounds(tree, *prim); let aabb = self.get_bounds(tree, *prim);
let diff = pos - (aabb.min + mat.cols.map(|x| x.reduce_min())); 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>, tree: &Store<Primitive>,
prim: Id<Primitive>, prim: Id<Primitive>,
pos: Vec3<i32>, pos: Vec3<i32>,
is_plot: impl Fn(Vec2<i32>) -> bool,
is_void: impl Fn(Vec2<i32>) -> bool,
) -> Option<Block> { ) -> Option<Block> {
if self.contains_at(tree, prim, pos) { if self.contains_at(tree, prim, pos, &is_plot, &is_void) {
match self { match self {
Fill::Block(block) => Some(*block), Fill::Block(block) => Some(*block),
Fill::Brick(bk, col, range) => Some(Block::new( Fill::Brick(bk, col, range) => Some(Block::new(
@ -146,7 +181,7 @@ impl Fill {
} }
Some(match &tree[prim] { Some(match &tree[prim] {
Primitive::Empty => return None, Primitive::Empty | Primitive::Plot | Primitive::Void => return None,
Primitive::Aabb(aabb) => *aabb, Primitive::Aabb(aabb) => *aabb,
Primitive::Pyramid { aabb, .. } => *aabb, Primitive::Pyramid { aabb, .. } => *aabb,
Primitive::Cylinder(aabb) => *aabb, Primitive::Cylinder(aabb) => *aabb,
@ -174,6 +209,7 @@ impl Fill {
self.get_bounds_inner(tree, *b), self.get_bounds_inner(tree, *b),
|a, b| a.intersection(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( Primitive::Or(a, b) | Primitive::Xor(a, b) => or_zip_with(
self.get_bounds_inner(tree, *a), self.get_bounds_inner(tree, *a),
self.get_bounds_inner(tree, *b), self.get_bounds_inner(tree, *b),
@ -189,6 +225,13 @@ impl Fill {
}; };
new_aabb.made_valid() 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> { pub fn bounds(&self) -> Aabr<i32> {
let border = 1; let border = 2;
Aabr { Aabr {
min: self.origin + self.tile_wpos(self.tiles.bounds.min - border), min: self.origin + self.tile_wpos(self.tiles.bounds.min - border),
max: self.origin + self.tile_wpos(self.tiles.bounds.max + 1 + 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); 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; let mut castles = 0;
@ -606,8 +606,8 @@ impl Site {
if let TileKind::Road { a, b, w } = &tile.kind { if let TileKind::Road { a, b, w } = &tile.kind {
if let Some(PlotKind::Road(path)) = tile.plot.map(|p| &self.plot(p).kind) { if let Some(PlotKind::Road(path)) = tile.plot.map(|p| &self.plot(p).kind) {
Some((LineSegment2 { Some((LineSegment2 {
start: self.tile_center_wpos(path.nodes()[*a 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), end: self.tile_center_wpos(path.nodes()[*b as usize]).map(|e| e as f32) - TILE_SIZE as f32 / 2.0,
}, *w)) }, *w))
} else { } else {
None None
@ -622,16 +622,19 @@ impl Site {
.map(|(line, w)| (line.distance_to_point(wpos2df) - w as f32 * 2.0).max(0.0)) .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); .min_by_key(|d| (*d * 100.0) as i32);
if dist.map_or(false, |d| d <= 0.75) { if let Some(col) = canvas.col(wpos2d).filter(|_| dist.map_or(false, |d| d <= 0.75)) {
let alt = canvas.col(wpos2d).map_or(0, |col| col.alt as i32); 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( (-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 { |b| if z >= 0 {
if b.is_filled() { if b.is_filled() {
Block::empty() Block::empty()
} else { } else {
b.with_sprite(SpriteKind::Empty) b.with_sprite(SpriteKind::Empty)
} }
} else if is_pier {
Block::new(BlockKind::Wood, Rgb::new(110, 65, 15))
} else { } else {
Block::new(BlockKind::Rock, Rgb::new(55, 45, 50)) 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 y in aabb.min.y..aabb.max.y {
for z in aabb.min.z..aabb.max.z { for z in aabb.min.z..aabb.max.z {
let pos = Vec3::new(x, y, 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); canvas.set(pos, block);
} }
} }

View File

@ -184,7 +184,7 @@ impl Structure for Castle {
max: (wpos + ts + 3 + roof_lip) max: (wpos + ts + 3 + roof_lip)
.with_z(tower_total_height + roof_height), .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), Fill::Brick(BlockKind::Wood, Rgb::new(40, 5, 11), 10),
); );

View File

@ -11,6 +11,7 @@ pub struct House {
alt: i32, alt: i32,
levels: u32, levels: u32,
roof_color: Rgb<u8>, roof_color: Rgb<u8>,
roof_inset: Vec2<bool>,
} }
impl House { impl House {
@ -42,6 +43,11 @@ impl House {
]; ];
*colors.choose(rng).unwrap() *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 storey = 5;
let roof = storey * self.levels as i32; let roof = storey * self.levels as i32;
let foundations = 12; 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 // Walls
let inner = prim(Primitive::Aabb(Aabb { let inner = prim(Primitive::Aabb(Aabb {
@ -66,10 +79,7 @@ impl Structure for House {
min: self.bounds.min.with_z(self.alt - foundations), min: self.bounds.min.with_z(self.alt - foundations),
max: (self.bounds.max + 1).with_z(self.alt + roof), max: (self.bounds.max + 1).with_z(self.alt + roof),
})); }));
fill( fill(outer, wall_block);
outer,
Fill::Brick(BlockKind::Rock, Rgb::new(80, 75, 85), 24),
);
fill(inner, Fill::Block(Block::empty())); fill(inner, Fill::Block(Block::empty()));
let walls = prim(Primitive::Xor(outer, inner)); let walls = prim(Primitive::Xor(outer, inner));
@ -97,10 +107,7 @@ impl Structure for House {
pillars_x = prim(Primitive::Or(pillars_x, pillar)); pillars_x = prim(Primitive::Or(pillars_x, pillar));
} }
let pillars = prim(Primitive::And(pillars_x, pillars_y)); let pillars = prim(Primitive::And(pillars_x, pillars_y));
fill( fill(pillars, structural_wood);
pillars,
Fill::Block(Block::new(BlockKind::Wood, Rgb::new(55, 25, 8))),
);
// For each storey... // For each storey...
for i in 0..self.levels + 1 { for i in 0..self.levels + 1 {
@ -147,13 +154,20 @@ impl Structure for House {
} }
// Floor // Floor
fill( let floor = prim(Primitive::Aabb(Aabb {
prim(Primitive::Aabb(Aabb {
min: (self.bounds.min + 1).with_z(self.alt + height), min: (self.bounds.min + 1).with_z(self.alt + height),
max: self.bounds.max.with_z(self.alt + height + 1), max: self.bounds.max.with_z(self.alt + height + 1),
})), }));
fill(
floor,
Fill::Block(Block::new(BlockKind::Rock, Rgb::new(89, 44, 14))), 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; let roof_lip = 2;
@ -165,23 +179,32 @@ impl Structure for House {
+ 1; + 1;
// Roof // Roof
fill( let roof_vol = prim(Primitive::Pyramid {
prim(Primitive::Pyramid {
aabb: Aabb { aabb: Aabb {
min: (self.bounds.min - roof_lip).with_z(self.alt + roof), 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), max: (self.bounds.max + 1 + roof_lip).with_z(self.alt + roof + roof_height),
}, },
inset: 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::And(tiles, can_spill)),
Fill::Block(Block::new(BlockKind::Wood, self.roof_color)), 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 // Foundations
fill( let foundations = prim(Primitive::Aabb(Aabb {
prim(Primitive::Aabb(Aabb {
min: (self.bounds.min - 1).with_z(self.alt - foundations), min: (self.bounds.min - 1).with_z(self.alt - foundations),
max: (self.bounds.max + 2).with_z(self.alt + 1), max: (self.bounds.max + 2).with_z(self.alt + 1),
})), }));
fill(
prim(Primitive::And(foundations, can_spill)),
Fill::Block(Block::new(BlockKind::Rock, Rgb::new(31, 33, 32))), 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_road(&self) -> bool { matches!(self.kind, TileKind::Road { .. }) }
pub fn is_obstacle(&self) -> bool { 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, self.kind,
TileKind::Hazard(_) | TileKind::Building | TileKind::Castle | TileKind::Wall(_) TileKind::Building | TileKind::Castle | TileKind::Wall(_)
) )
} }
} }