From 78aa587722fadc442caecebcb9617795a1e6563e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 2 May 2021 20:32:52 +0100 Subject: [PATCH] Better house roofs --- Cargo.toml | 2 +- world/src/civ/mod.rs | 2 +- world/src/site2/gen.rs | 69 +++++++++++++++++++++++++++------- world/src/site2/mod.rs | 23 ++++++++---- world/src/site2/plot/castle.rs | 2 +- world/src/site2/plot/house.rs | 69 ++++++++++++++++++++++------------ world/src/site2/tile.rs | 11 +++++- 7 files changed, 129 insertions(+), 49 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5e1c09240b..79e44a1b68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 077707fd57..8b7e834e5f 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -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), }; diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs index f958b8732a..7a73577bc9 100644 --- a/world/src/site2/gen.rs +++ b/world/src/site2/gen.rs @@ -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), - Pyramid { aabb: Aabb, inset: i32 }, + Pyramid { aabb: Aabb, inset: Vec2 }, Cylinder(Aabb), Cone(Aabb), Sphere(Aabb), @@ -20,21 +23,31 @@ pub enum Primitive { // Combinators And(Id, Id), + AndNot(Id, Id), // Not second Or(Id, Id), Xor(Id, Id), // Not commutative Diff(Id, Id), // Operators Rotate(Id, Mat3), + Offset(Id, Vec3), } +#[derive(Copy, Clone)] pub enum Fill { Block(Block), Brick(BlockKind, Rgb, u8), } impl Fill { - fn contains_at(&self, tree: &Store, prim: Id, pos: Vec3) -> bool { + fn contains_at( + &self, + tree: &Store, + prim: Id, + pos: Vec3, + is_plot: &impl Fn(Vec2) -> bool, + is_void: &impl Fn(Vec2) -> bool, + ) -> bool { // Custom closure because vek's impl of `contains_point` is inclusive :( let aabb_contains = |aabb: Aabb, pos: Vec3| { (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, prim: Id, pos: Vec3, + is_plot: impl Fn(Vec2) -> bool, + is_void: impl Fn(Vec2) -> bool, ) -> Option { - 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, + } + }, }) } diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 6a65ebc154..5c9e792540 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -67,7 +67,7 @@ impl Site { } pub fn bounds(&self) -> Aabr { - 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); } } diff --git a/world/src/site2/plot/castle.rs b/world/src/site2/plot/castle.rs index e4459bda6f..e9e5ef63e7 100644 --- a/world/src/site2/plot/castle.rs +++ b/world/src/site2/plot/castle.rs @@ -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), ); diff --git a/world/src/site2/plot/house.rs b/world/src/site2/plot/house.rs index 7bc85fc8c0..873da73d99 100644 --- a/world/src/site2/plot/house.rs +++ b/world/src/site2/plot/house.rs @@ -11,6 +11,7 @@ pub struct House { alt: i32, levels: u32, roof_color: Rgb, + roof_inset: Vec2, } 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))), ); } diff --git a/world/src/site2/tile.rs b/world/src/site2/tile.rs index 1d0f759deb..fd82266926 100644 --- a/world/src/site2/tile.rs +++ b/world/src/site2/tile.rs @@ -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(_) ) } }