From a5ccfe3bc9453c6776cf6722b2e96813cbb46661 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 13 Apr 2020 12:38:47 +0100 Subject: [PATCH] Significantly improved house variation --- .../settlement/building/archetype/house.rs | 220 +++++++++++------- .../settlement/building/archetype/keep.rs | 15 +- .../site/settlement/building/archetype/mod.rs | 50 +++- world/src/site/settlement/building/mod.rs | 4 +- .../src/site/settlement/building/skeleton.rs | 37 +-- 5 files changed, 217 insertions(+), 109 deletions(-) diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index c3d15ccf31..859f725845 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -7,6 +7,7 @@ use common::{ use crate::util::{RandomField, Sampler}; use super::{ Archetype, + BlockMask, super::skeleton::*, }; @@ -17,16 +18,45 @@ pub struct House { roof_ribbing: bool, } +enum RoofStyle { + Hip, + Gable, + Rounded, +} + +enum StoreyFill { + None, + Upper, + All, +} + +impl StoreyFill { + fn has_lower(&self) -> bool { !if let StoreyFill::None = self { true } else { false } } + fn has_upper(&self) -> bool { if let StoreyFill::None = self { false } else { true } } +} + pub struct Attr { central_supports: bool, - lower_walls: bool, + storey_fill: StoreyFill, + roof_style: RoofStyle, + mansard: i32, } impl Attr { fn generate(rng: &mut R) -> Self { Self { central_supports: rng.gen(), - lower_walls: rng.gen(), + storey_fill: match rng.gen_range(0, 2) { + //0 => StoreyFill::None, + 0 => StoreyFill::Upper, + _ => StoreyFill::All, + }, + roof_style: match rng.gen_range(0, 3) { + 0 => RoofStyle::Hip, + 1 => RoofStyle::Gable, + _ => RoofStyle::Rounded, + }, + mansard: rng.gen_range(-8, 6).max(0), } } } @@ -35,38 +65,31 @@ impl Archetype for House { type Attr = Attr; fn generate(rng: &mut R) -> (Self, Skeleton) { - let this = Self { - roof_color: Rgb::new( - rng.gen_range(50, 200), - rng.gen_range(50, 200), - rng.gen_range(50, 200), - ), - noise: RandomField::new(rng.gen()), - chimney: if rng.gen() { Some(rng.gen_range(1, 6)) } else { None }, - roof_ribbing: rng.gen(), - }; - - let len = rng.gen_range(-8, 20).clamped(0, 16); - let branches_per_side = 1 + len as usize / 16; + let len = rng.gen_range(-8, 24).clamped(0, 20); + let locus = 6 + rng.gen_range(0, 5); + let branches_per_side = 1 + len as usize / 20; let skel = Skeleton { offset: -rng.gen_range(0, len + 7).clamped(0, len), ori: if rng.gen() { Ori::East } else { Ori::North }, root: Branch { len, attr: Attr { - central_supports: rng.gen(), - lower_walls: true, + storey_fill: StoreyFill::All, + mansard: 0, + ..Attr::generate(rng) }, - locus: 8 + rng.gen_range(0, 5), + locus, + border: 4, children: [1, -1] .iter() .map(|flip| (0..branches_per_side).map(move |i| (i, *flip))) .flatten() - .filter_map(move |(i, flip)| if rng.gen() { + .filter_map(|(i, flip)| if rng.gen() { Some((i as i32 * len / (branches_per_side - 1).max(1) as i32, Branch { - len: rng.gen_range(0, 12) * flip, + len: rng.gen_range(5, 16) * flip, attr: Attr::generate(rng), - locus: 8 + rng.gen_range(0, 3), + locus: (6 + rng.gen_range(0, 3)).min(locus), + border: 4, children: Vec::new(), })) } else { @@ -76,6 +99,17 @@ impl Archetype for House { }, }; + let this = Self { + roof_color: Rgb::new( + rng.gen_range(50, 200), + rng.gen_range(50, 200), + rng.gen_range(50, 200), + ), + noise: RandomField::new(rng.gen()), + chimney: if rng.gen() { Some(8 + skel.root.locus + rng.gen_range(1, 5)) } else { None }, + roof_ribbing: rng.gen(), + }; + (this, skel) } @@ -86,44 +120,45 @@ impl Archetype for House { center_offset: Vec2, z: i32, branch: &Branch, - ) -> Option> { + ) -> BlockMask { let profile = Vec2::new(bound_offset.x, z); let make_block = |r, g, b| { let nz = self.noise.get(Vec3::new(center_offset.x, center_offset.y, z * 8)); - Some(Some(Block::new(BlockKind::Normal, Rgb::new(r, g, b) + (nz & 0x0F) as u8 - 8))) + BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b) + (nz & 0x0F) as u8 - 8), 2) }; let foundation = make_block(100, 100, 100); let log = make_block(60, 45, 30); - let floor = make_block(100, 75, 50); + let floor = make_block(100, 75, 50).with_priority(3); let wall = make_block(200, 180, 150); let roof = make_block(self.roof_color.r, self.roof_color.g, self.roof_color.b); - let empty = Some(Some(Block::empty())); - let fire = Some(Some(Block::new(BlockKind::Ember, Rgb::white()))); + let empty = BlockMask::nothing(); + let internal = BlockMask::new(Block::empty(), 4); + let fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), 2); let ceil_height = 6; - let lower_width = -3 + branch.locus; - let upper_width = -2 + branch.locus; + let lower_width = branch.locus - 1; + let upper_width = branch.locus; let width = if profile.y >= ceil_height { upper_width } else { lower_width }; let foundation_height = 0 - (dist - width - 1).max(0); - let roof_height = 8 + width; + let roof_top = 8 + width; - if let Some(chimney_height) = self.chimney { + if let Some(chimney_top) = self.chimney { // Chimney shaft if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y >= foundation_height + 1 { return if profile.y == foundation_height + 1 { fire } else { - empty + internal }; } // Chimney - if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < roof_height + chimney_height { + if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < chimney_top { // Fireplace if center_offset.product() == 0 && profile.y > foundation_height + 1 && profile.y <= foundation_height + 3 { - return empty; + return internal; } else { return foundation; } @@ -131,7 +166,7 @@ impl Archetype for House { } if profile.y <= foundation_height && dist < width + 3 { // Foundations - if branch.attr.lower_walls { + if branch.attr.storey_fill.has_lower() { if dist == width - 1 { // Floor lining return log; } else if dist < width - 1 && profile.y == foundation_height { // Floor @@ -140,88 +175,103 @@ impl Archetype for House { } if dist < width && profile.y < foundation_height && profile.y >= foundation_height - 3 { // Basement - return empty; + return internal; } else { - return foundation; + return foundation.with_priority(1); } } - let do_roof = |profile: Vec2, dist, roof_height, roof_width| { - if profile.y > roof_height - profile.x { // Air above roof - return Some(Some(None)); + let do_roof = |profile: Vec2, dist, roof_top, roof_width, mansard| { + if profile.y > roof_top - profile.x.max(mansard) && profile.y >= roof_top - roof_width { // Air above roof + return Some(empty); } // Roof - if profile.y == roof_height - profile.x + if profile.y == roof_top - profile.x.max(mansard) && dist <= roof_width { - let is_ribbing = (roof_height - profile.y) % 3 == 0 && self.roof_ribbing; - if profile.x == 0 || dist == roof_width|| is_ribbing { // Eaves - return Some(log); + let is_ribbing = (profile.y - ceil_height) % 3 == 0 && self.roof_ribbing; + if (profile.x == 0 && mansard == 0) || dist == roof_width|| is_ribbing { // Eaves + return Some(log.with_priority(1)); } else { - return Some(roof); + return Some(roof.with_priority(1)); } } None }; - if let Some(block) = do_roof(profile, dist, roof_height, width + 2) { + if let Some(block) = match &branch.attr.roof_style { + RoofStyle::Hip => do_roof(Vec2::new(dist, profile.y), dist, roof_top, width + 2, branch.attr.mansard), + RoofStyle::Gable => do_roof(profile, dist, roof_top, width + 2, branch.attr.mansard), + RoofStyle::Rounded => { + let circular_dist = (bound_offset.map(|e| e.pow(4) as f32).sum().powf(0.25) + 0.5).ceil() as i32; + do_roof(Vec2::new(circular_dist, profile.y), circular_dist, roof_top, width + 2, branch.attr.mansard) + }, + } { return block; } // Walls - if dist == width && ( - bound_offset.x == bound_offset.y || - (profile.x == 0 && branch.attr.central_supports) || - profile.y == ceil_height - ) { // Support beams - return log; - } else if !branch.attr.lower_walls && profile.y < ceil_height { - return None; - } else if dist == width { - let frame_bounds = if profile.y >= ceil_height { - Aabr { - min: Vec2::new(-1, ceil_height + 2), - max: Vec2::new(1, ceil_height + 5), - } + if dist == width { + if bound_offset.x == bound_offset.y || profile.y == ceil_height { // Support beams + return log; + } else if !branch.attr.storey_fill.has_lower() && profile.y < ceil_height { + return empty; + } else if !branch.attr.storey_fill.has_upper() { + return empty; } else { - Aabr { - min: Vec2::new(2, foundation_height + 2), - max: Vec2::new(width - 2, ceil_height - 2), - } - }; - let window_bounds = Aabr { - min: (frame_bounds.min + 1).map2(frame_bounds.center(), |a, b| a.min(b)), - max: (frame_bounds.max - 1).map2(frame_bounds.center(), |a, b| a.max(b)), - }; + let frame_bounds = if profile.y >= ceil_height { + Aabr { + min: Vec2::new(-1, ceil_height + 2), + max: Vec2::new(1, ceil_height + 5), + } + } else { + Aabr { + min: Vec2::new(2, foundation_height + 2), + max: Vec2::new(width - 2, ceil_height - 2), + } + }; + let window_bounds = Aabr { + min: (frame_bounds.min + 1).map2(frame_bounds.center(), |a, b| a.min(b)), + max: (frame_bounds.max - 1).map2(frame_bounds.center(), |a, b| a.max(b)), + }; - // Window - if (frame_bounds.size() + 1).reduce_min() > 2 { // Window frame is large enough for a window - let surface_pos = Vec2::new(bound_offset.x, profile.y); - if window_bounds.contains_point(surface_pos) { - return empty; - } else if frame_bounds.contains_point(surface_pos) { - return log; + // Window + if (frame_bounds.size() + 1).reduce_min() > 2 { // Window frame is large enough for a window + let surface_pos = Vec2::new(bound_offset.x, profile.y); + if window_bounds.contains_point(surface_pos) { + return internal; + } else if frame_bounds.contains_point(surface_pos) { + return log.with_priority(3); + }; + } + + // Wall + return if branch.attr.central_supports && profile.x == 0 { // Support beams + log.with_priority(4) + } else { + wall }; } - - // Wall - return wall; } if dist < width { // Internals - return if profile.y == ceil_height { + if profile.y == ceil_height { if profile.x == 0 {// Rafters - log - } else { // Ceiling - floor + return log; + } else if branch.attr.storey_fill.has_upper() { // Ceiling + return floor; } + } else if (!branch.attr.storey_fill.has_lower() && profile.y < ceil_height) + || (!branch.attr.storey_fill.has_upper() && profile.y >= ceil_height) + { + return empty; } else { - empty - }; + return internal; + } } - None + empty } } diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index 887e2ba8c0..52c48f09c2 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -6,6 +6,7 @@ use common::{ }; use super::{ Archetype, + BlockMask, super::skeleton::*, }; @@ -22,12 +23,14 @@ impl Archetype for Keep { root: Branch { len, attr: Self::Attr::default(), - locus: 8 + rng.gen_range(0, 5), + locus: 5 + rng.gen_range(0, 5), + border: 3, children: (0..rng.gen_range(0, 4)) .map(|_| (rng.gen_range(-5, len + 5).clamped(0, len.max(1) - 1), Branch { len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 }, attr: Self::Attr::default(), - locus: 8 + rng.gen_range(0, 3), + locus: 5 + rng.gen_range(0, 3), + border: 3, children: Vec::new(), })) .collect(), @@ -44,20 +47,20 @@ impl Archetype for Keep { center_offset: Vec2, z: i32, branch: &Branch, - ) -> Option> { + ) -> BlockMask { let profile = Vec2::new(bound_offset.x, z); let make_block = |r, g, b| { - Some(Some(Block::new(BlockKind::Normal, Rgb::new(r, g, b)))) + BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b)), 2) }; let foundation = make_block(100, 100, 100); let log = make_block(60, 45, 30); let wall = make_block(75, 100, 125); let roof = make_block(150, 120, 50); - let empty = Some(Some(Block::empty())); + let empty = BlockMask::new(Block::empty(), 2); - let width = 3 + branch.locus; + let width = branch.locus; let rampart_width = 5 + branch.locus; let roof_height = 12 + width; let ceil_height = 16; diff --git a/world/src/site/settlement/building/archetype/mod.rs b/world/src/site/settlement/building/archetype/mod.rs index 0dbf6b3254..667f965865 100644 --- a/world/src/site/settlement/building/archetype/mod.rs +++ b/world/src/site/settlement/building/archetype/mod.rs @@ -3,9 +3,55 @@ pub mod keep; use vek::*; use rand::prelude::*; -use common::terrain::Block; +use common::{terrain::Block, vol::Vox}; use super::skeleton::*; +#[derive(Copy, Clone)] +pub struct BlockMask { + block: Block, + priority: i32, +} + +impl BlockMask { + pub fn new(block: Block, priority: i32) -> Self { + Self { block, priority } + } + + pub fn nothing() -> Self { + Self { + block: Block::empty(), + priority: 0, + } + } + + pub fn with_priority(mut self, priority: i32) -> Self { + self.priority = priority; + self + } + + pub fn resolve_with(self, dist_self: i32, other: Self, dist_other: i32) -> Self { + if self.priority == other.priority { + if dist_self <= dist_other { + self + } else { + other + } + } else if self.priority >= other.priority { + self + } else { + other + } + } + + pub fn finish(self) -> Option { + if self.priority > 0 { + Some(self.block) + } else { + None + } + } +} + pub trait Archetype { type Attr; @@ -17,5 +63,5 @@ pub trait Archetype { center_offset: Vec2, z: i32, branch: &Branch, - ) -> Option>; + ) -> BlockMask; } diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs index 1aaff09e72..3ddc7c3f04 100644 --- a/world/src/site/settlement/building/mod.rs +++ b/world/src/site/settlement/building/mod.rs @@ -48,8 +48,8 @@ impl Building { pub fn sample(&self, pos: Vec3) -> Option { let rpos = pos - self.origin; - self.skel.closest(rpos.into(), |dist, bound_offset, center_offset, branch| { + self.skel.sample_closest(rpos.into(), |dist, bound_offset, center_offset, branch| { self.archetype.draw(dist, bound_offset, center_offset, rpos.z, branch) - }).flatten() + }).finish() } } diff --git a/world/src/site/settlement/building/skeleton.rs b/world/src/site/settlement/building/skeleton.rs index 882d8f5ce6..cde77ebb49 100644 --- a/world/src/site/settlement/building/skeleton.rs +++ b/world/src/site/settlement/building/skeleton.rs @@ -1,4 +1,5 @@ use vek::*; +use super::archetype::BlockMask; #[derive(Copy, Clone, PartialEq, Eq)] pub enum Ori { @@ -26,14 +27,15 @@ pub struct Branch { pub len: i32, pub attr: T, pub locus: i32, + pub border: i32, pub children: Vec<(i32, Branch)>, } impl Branch { - fn for_each<'a>(&'a self, node: Vec2, ori: Ori, f: &mut impl FnMut(Vec2, Ori, &'a Branch)) { - f(node, ori, self); + fn for_each<'a>(&'a self, node: Vec2, ori: Ori, is_child: bool, parent_locus: i32, f: &mut impl FnMut(Vec2, Ori, &'a Branch, bool, i32)) { + f(node, ori, self, is_child, parent_locus); for (offset, child) in &self.children { - child.for_each(node + ori.dir() * *offset, ori.flip(), f); + child.for_each(node + ori.dir() * *offset, ori.flip(), true, self.locus, f); } } } @@ -45,27 +47,28 @@ pub struct Skeleton { } impl Skeleton { - pub fn for_each<'a>(&'a self, mut f: impl FnMut(Vec2, Ori, &'a Branch)) { - self.root.for_each(self.ori.dir() * self.offset, self.ori, &mut f); + pub fn for_each<'a>(&'a self, mut f: impl FnMut(Vec2, Ori, &'a Branch, bool, i32)) { + self.root.for_each(self.ori.dir() * self.offset, self.ori, false, 0, &mut f); } pub fn bounds(&self) -> Aabr { let mut bounds = Aabr::new_empty(self.ori.dir() * self.offset); - self.for_each(|node, ori, branch| { + self.for_each(|node, ori, branch, _, _| { let node2 = node + ori.dir() * branch.len; - let a = node.map2(node2, |a, b| a.min(b)) - branch.locus; - let b = node.map2(node2, |a, b| a.max(b)) + branch.locus; + let a = node.map2(node2, |a, b| a.min(b)) - (branch.locus + branch.border); + let b = node.map2(node2, |a, b| a.max(b)) + (branch.locus + branch.border); bounds.expand_to_contain_point(a); bounds.expand_to_contain_point(b); }); bounds } - pub fn closest(&self, pos: Vec2, mut f: impl FnMut(i32, Vec2, Vec2, &Branch) -> Option) -> Option { - let mut min = None; - self.for_each(|node, ori, branch| { + pub fn sample_closest(&self, pos: Vec2, mut f: impl FnMut(i32, Vec2, Vec2, &Branch) -> BlockMask) -> BlockMask { + let mut min = None::<(_, BlockMask)>; + self.for_each(|node, ori, branch, is_child, parent_locus| { let node2 = node + ori.dir() * branch.len; + let node = node + if is_child { ori.dir() * branch.len.signum() * (branch.locus - parent_locus).clamped(0, branch.len.abs()) } else { Vec2::zero() }; let bounds = Aabr::new_empty(node) .expanded_to_contain_point(node2); let bound_offset = if ori == Ori::East { @@ -86,10 +89,16 @@ impl Skeleton { }; let dist = bound_offset.reduce_max(); let dist_locus = dist - branch.locus; - if min.as_ref().map(|(min_dist_locus, _)| dist_locus < *min_dist_locus).unwrap_or(true) { - min = f(dist, bound_offset, center_offset, branch).map(|r| (dist_locus, r)).or(min.clone()); + if !is_child || match ori { + Ori::East => (pos.x - node.x) * branch.len.signum() >= 0, + Ori::North => (pos.y - node.y) * branch.len.signum() >= 0, + } || true { + let new_bm = f(dist, bound_offset, center_offset, branch); + min = min + .map(|(min_dist_locus, bm)| (dist_locus, bm.resolve_with(min_dist_locus, new_bm, dist_locus))) + .or(Some((dist_locus, new_bm))); } }); - min.map(|(_, r)| r) + min.map(|(_, bm)| bm).unwrap_or(BlockMask::nothing()) } }