Significantly improved house variation

This commit is contained in:
Joshua Barretto 2020-04-13 12:38:47 +01:00
parent c1945a1445
commit a5ccfe3bc9
5 changed files with 217 additions and 109 deletions

View File

@ -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<R: Rng>(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<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>) {
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<i32>,
z: i32,
branch: &Branch<Self::Attr>,
) -> Option<Option<Block>> {
) -> 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<i32>, dist, roof_height, roof_width| {
if profile.y > roof_height - profile.x { // Air above roof
return Some(Some(None));
let do_roof = |profile: Vec2<i32>, 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
}
}

View File

@ -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<i32>,
z: i32,
branch: &Branch<Self::Attr>,
) -> Option<Option<Block>> {
) -> 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;

View File

@ -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<Block> {
if self.priority > 0 {
Some(self.block)
} else {
None
}
}
}
pub trait Archetype {
type Attr;
@ -17,5 +63,5 @@ pub trait Archetype {
center_offset: Vec2<i32>,
z: i32,
branch: &Branch<Self::Attr>,
) -> Option<Option<Block>>;
) -> BlockMask;
}

View File

@ -48,8 +48,8 @@ impl<A: Archetype> Building<A> {
pub fn sample(&self, pos: Vec3<i32>) -> Option<Block> {
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()
}
}

View File

@ -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<T> {
pub len: i32,
pub attr: T,
pub locus: i32,
pub border: i32,
pub children: Vec<(i32, Branch<T>)>,
}
impl<T> Branch<T> {
fn for_each<'a>(&'a self, node: Vec2<i32>, ori: Ori, f: &mut impl FnMut(Vec2<i32>, Ori, &'a Branch<T>)) {
f(node, ori, self);
fn for_each<'a>(&'a self, node: Vec2<i32>, ori: Ori, is_child: bool, parent_locus: i32, f: &mut impl FnMut(Vec2<i32>, Ori, &'a Branch<T>, 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<T> {
}
impl<T> Skeleton<T> {
pub fn for_each<'a>(&'a self, mut f: impl FnMut(Vec2<i32>, Ori, &'a Branch<T>)) {
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<i32>, Ori, &'a Branch<T>, bool, i32)) {
self.root.for_each(self.ori.dir() * self.offset, self.ori, false, 0, &mut f);
}
pub fn bounds(&self) -> Aabr<i32> {
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<R: Clone>(&self, pos: Vec2<i32>, mut f: impl FnMut(i32, Vec2<i32>, Vec2<i32>, &Branch<T>) -> Option<R>) -> Option<R> {
let mut min = None;
self.for_each(|node, ori, branch| {
pub fn sample_closest(&self, pos: Vec2<i32>, mut f: impl FnMut(i32, Vec2<i32>, Vec2<i32>, &Branch<T>) -> 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<T> Skeleton<T> {
};
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())
}
}