From 149d6d05af7d8c2ac80269c399b30d25a2b71679 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 10 Apr 2020 12:16:30 +0100 Subject: [PATCH] Added skeleton house generation --- world/src/sim/mod.rs | 4 + .../settlement/building/archetype/house.rs | 86 +++++++++++++++++++ .../settlement/building/archetype/keep.rs | 53 ++++++++++++ .../site/settlement/building/archetype/mod.rs | 20 +++++ world/src/site/settlement/building/mod.rs | 70 +++++++++++++++ .../src/site/settlement/building/skeleton.rs | 64 +++++++++----- world/src/site/settlement/mod.rs | 78 +++++++++++------ 7 files changed, 326 insertions(+), 49 deletions(-) create mode 100644 world/src/site/settlement/building/archetype/house.rs create mode 100644 world/src/site/settlement/building/archetype/keep.rs create mode 100644 world/src/site/settlement/building/archetype/mod.rs diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index ea7b8c5039..b210f52766 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1568,6 +1568,10 @@ impl WorldSim { } } + pub fn get_alt_approx(&self, pos: Vec2) -> Option { + self.get_interpolated(pos, |chunk| chunk.alt) + } + pub fn get_wpos(&self, wpos: Vec2) -> Option<&SimChunk> { self.get( wpos.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| { diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs new file mode 100644 index 0000000000..0d972d9beb --- /dev/null +++ b/world/src/site/settlement/building/archetype/house.rs @@ -0,0 +1,86 @@ +use vek::*; +use rand::prelude::*; +use common::{ + terrain::{Block, BlockKind}, + vol::Vox, +}; +use super::{ + Archetype, + super::skeleton::*, +}; + +pub struct House { + roof_color: Rgb, +} + +impl Archetype for House { + type Attr = (); + + fn generate(rng: &mut R) -> Self { + Self { + roof_color: Rgb::new( + rng.gen_range(50, 200), + rng.gen_range(50, 200), + rng.gen_range(50, 200), + ), + } + } + + fn draw( + &self, + dist: i32, + offset: Vec2, + z: i32, + branch: &Branch, + ) -> Option { + let profile = Vec2::new(offset.x, z); + + let foundation = Block::new(BlockKind::Normal, Rgb::new(100, 100, 100)); + let log = Block::new(BlockKind::Normal, Rgb::new(60, 45, 30)); + let floor = Block::new(BlockKind::Normal, Rgb::new(100, 75, 50)); + let wall = Block::new(BlockKind::Normal, Rgb::new(200, 180, 150)); + let roof = Block::new(BlockKind::Normal, self.roof_color); + let empty = Block::empty(); + + let width = 3 + branch.locus; + let roof_height = 8 + width; + let ceil_height = 6; + + if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 { // Foundations + if dist < width { // Floor + Some(floor) + } else { + Some(foundation) + } + } else if profile.y > roof_height - profile.x { // Air above roof + None + } else if profile.y == roof_height - profile.x + && profile.y >= ceil_height + && dist <= width + 2 + { // Roof + if profile.x == 0 || dist == width + 2 { // Eaves + Some(log) + } else { + Some(roof) + } + } else if dist == width { // Wall + if offset.x == offset.y || profile.y == ceil_height || offset.x == 0 { + Some(log) + } else { + Some(wall) + } + } else if dist < width { // Internals + if profile.y == ceil_height { + if profile.x == 0 {// Rafters + Some(log) + } else { // Ceiling + Some(floor) + } + } else { + Some(empty) + } + } else { + None + } + } +} diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs new file mode 100644 index 0000000000..77817d44c0 --- /dev/null +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -0,0 +1,53 @@ +use vek::*; +use rand::prelude::*; +use common::{ + terrain::{Block, BlockKind}, + vol::Vox, +}; +use super::{ + Archetype, + super::skeleton::*, +}; + +pub struct Keep; + +impl Archetype for Keep { + type Attr = (); + + fn generate(rng: &mut R) -> Self { + Self + } + + fn draw( + &self, + dist: i32, + offset: Vec2, + z: i32, + branch: &Branch, + ) -> Option { + let profile = Vec2::new(offset.x, z); + + let foundation = Block::new(BlockKind::Normal, Rgb::new(100, 100, 100)); + let log = Block::new(BlockKind::Normal, Rgb::new(60, 45, 30)); + let wall = Block::new(BlockKind::Normal, Rgb::new(75, 100, 125)); + let roof = Block::new(BlockKind::Normal, Rgb::new(150, 120, 50)); + let empty = Block::empty(); + + let width = 3 + branch.locus; + let rampart_width = 5 + branch.locus; + let roof_height = 12 + width; + let ceil_height = 8; + + if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 { // Foundations + Some(foundation) + } else if profile.y == ceil_height && dist < rampart_width { + Some(roof) + } else if dist == rampart_width && profile.y >= ceil_height && profile.y < ceil_height + 4 { + Some(wall) + } else if dist == width && profile.y <= ceil_height { + Some(wall) + } else { + None + } + } +} diff --git a/world/src/site/settlement/building/archetype/mod.rs b/world/src/site/settlement/building/archetype/mod.rs new file mode 100644 index 0000000000..bb427798ff --- /dev/null +++ b/world/src/site/settlement/building/archetype/mod.rs @@ -0,0 +1,20 @@ +pub mod house; +pub mod keep; + +use vek::*; +use rand::prelude::*; +use common::terrain::Block; +use super::skeleton::*; + +pub trait Archetype { + type Attr: Default; + + fn generate(rng: &mut R) -> Self where Self: Sized; + fn draw( + &self, + dist: i32, + offset: Vec2, + z: i32, + branch: &Branch, + ) -> Option; +} diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs index 416c94767a..1eebfb02f9 100644 --- a/world/src/site/settlement/building/mod.rs +++ b/world/src/site/settlement/building/mod.rs @@ -1 +1,71 @@ mod skeleton; +mod archetype; + +// Reexports +pub use self::archetype::Archetype; + +use vek::*; +use rand::prelude::*; +use self::skeleton::*; +use common::terrain::Block; + +pub type HouseBuilding = Building; + +pub struct Building { + skel: Skeleton, + archetype: A, + origin: Vec3, +} + +impl Building { + pub fn generate(rng: &mut impl Rng, origin: Vec3) -> Self + where A: Sized + { + let len = rng.gen_range(-8, 12).max(0); + let archetype = A::generate(rng); + Self { + skel: Skeleton { + offset: -len / 2, + ori: Ori::East, + root: Branch { + len, + attr: A::Attr::default(), + locus: 3 + rng.gen_range(0, 6), + children: (0..rng.gen_range(1, 3)) + .map(|_| (rng.gen_range(0, len + 1), Branch { + len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 }, + attr: A::Attr::default(), + locus: 1 + rng.gen_range(0, 3), + children: Vec::new(), + })) + .collect(), + }, + }, + archetype, + origin, + } + } + + pub fn bounds_2d(&self) -> Aabr { + let b = self.skel.bounds(); + Aabr { + min: Vec2::from(self.origin) + b.min - 12, + max: Vec2::from(self.origin) + b.max + 12, + } + } + + pub fn bounds(&self) -> Aabb { + let aabr = self.bounds_2d(); + Aabb { + min: Vec3::from(aabr.min) + Vec3::unit_z() * (self.origin.z - 5), + max: Vec3::from(aabr.max) + Vec3::unit_z() * (self.origin.z + 32), + } + } + + pub fn sample(&self, pos: Vec3) -> Option { + let rpos = pos - self.origin; + let (dist, offset, branch) = self.skel.closest(rpos.into()); + + self.archetype.draw(dist, offset, rpos.z, branch) + } +} diff --git a/world/src/site/settlement/building/skeleton.rs b/world/src/site/settlement/building/skeleton.rs index e278136307..08a2fb14f1 100644 --- a/world/src/site/settlement/building/skeleton.rs +++ b/world/src/site/settlement/building/skeleton.rs @@ -1,6 +1,6 @@ use vek::*; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Eq)] pub enum Ori { East, North, @@ -22,14 +22,15 @@ impl Ori { } } -pub struct Branch { - len: i32, - locus: i32, - children: Vec<(i32, Branch)>, +pub struct Branch { + pub len: i32, + pub attr: T, + pub locus: 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)) { +impl Branch { + fn for_each<'a>(&'a self, node: Vec2, ori: Ori, f: &mut impl FnMut(Vec2, Ori, &'a Branch)) { f(node, ori, self); for (offset, child) in &self.children { child.for_each(node + ori.dir() * *offset, ori.flip(), f); @@ -37,28 +38,49 @@ impl Branch { } } -pub struct Skeleton { - offset: i32, - ori: Ori, - root: Branch, +pub struct Skeleton { + pub offset: i32, + pub ori: Ori, + pub root: Branch, } -impl Skeleton { - pub fn for_each<'a>(&'a self, mut f: impl FnMut(Vec2, Ori, &'a Branch)) { +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 closest(&self, pos: Vec2) -> (i32, &Branch) { + pub fn bounds(&self) -> Aabr { + let mut bounds = Aabr::new_empty(self.ori.dir() * self.offset); + self.for_each(|node, ori, branch| { + bounds.expand_to_contain(Aabr::new_empty(node - ori.flip().dir() * branch.locus) + .expanded_to_contain_point(node + ori.dir() * branch.len + ori.flip().dir() * branch.locus)); + }); + bounds + } + + pub fn closest(&self, pos: Vec2) -> (i32, Vec2, &Branch) { let mut min = None; self.for_each(|node, ori, branch| { - let bounds = Aabr::new_empty(node - ori.flip().dir() * branch.locus) - .expanded_to_contain_point(node + ori.dir() * branch.len + ori.flip().dir() * branch.locus); - let projected = pos.map2(bounds.min.zip(bounds.max), |e, (min, max)| Clamp::clamp(e, min, max)); - let dist = (projected - pos).map(|e| e.abs()).reduce_max(); - if min.map(|(min_dist, _)| dist < min_dist).unwrap_or(true) { - min = Some((dist, branch)); + let node2 = node + ori.dir() * branch.len; + let bounds = Aabr::new_empty(node) + .expanded_to_contain_point(node2); + let offs = if ori == Ori::East { + Vec2::new( + node.y - pos.y, + pos.x - pos.x.clamped(bounds.min.x, bounds.max.x) + ) + } else { + Vec2::new( + node.x - pos.x, + pos.y - pos.y.clamped(bounds.min.y, bounds.max.y) + ) + }.map(|e| e.abs()); + let dist = offs.reduce_max(); + let dist_locus = dist - branch.locus; + if min.map(|(min_dist_locus, _, _, _)| dist_locus < min_dist_locus).unwrap_or(true) { + min = Some((dist_locus, dist, offs, branch)); } }); - min.unwrap() + min.map(|(_, dist, offs, branch)| (dist, offs, branch)).unwrap() } } diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 37b0be67b9..7192541533 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -5,6 +5,7 @@ use crate::{ sim::{SimChunk, WorldSim}, util::{Grid, RandomField, Sampler, StructureGen2d}, }; +use self::building::HouseBuilding; use super::SpawnRules; use common::{ astar::Astar, @@ -81,12 +82,11 @@ const AREA_SIZE: u32 = 32; fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as i32 } pub enum StructureKind { - House, + House(HouseBuilding), } pub struct Structure { kind: StructureKind, - bounds: Aabr, } pub struct Settlement { @@ -105,25 +105,31 @@ pub struct Farm { base_tile: Vec2, } +pub struct GenCtx<'a, R: Rng> { + sim: Option<&'a WorldSim>, + rng: &'a mut R, +} + impl Settlement { pub fn generate(wpos: Vec2, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self { + let mut ctx = GenCtx { sim, rng }; let mut this = Self { origin: wpos, - land: Land::new(rng), + land: Land::new(ctx.rng), farms: Store::default(), structures: Vec::new(), town: None, }; - if let Some(sim) = sim { - this.designate_from_world(sim, rng); + if let Some(sim) = ctx.sim { + this.designate_from_world(sim, ctx.rng); } //this.place_river(rng); - this.place_farms(rng); - this.place_town(rng); - this.place_paths(rng); + this.place_farms(&mut ctx); + this.place_town(ctx.rng); + this.place_paths(ctx.rng); this } @@ -295,8 +301,8 @@ impl Settlement { .write_path(&wall_path, WayKind::Wall, buildable, true); } - pub fn place_farms(&mut self, rng: &mut impl Rng) { - const FARM_COUNT: usize = 4; + pub fn place_farms(&mut self, ctx: &mut GenCtx) { + const FARM_COUNT: usize = 6; const FIELDS_PER_FARM: usize = 5; for _ in 0..FARM_COUNT { @@ -309,23 +315,26 @@ impl Settlement { self.land.set(base_tile, farmhouse); // Farmhouses - for _ in 0..rng.gen_range(1, 3) { + for _ in 0..ctx.rng.gen_range(1, 3) { let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2) - + Vec2::new(rng.gen_range(-16, 16), rng.gen_range(-16, 16)); + + Vec2::new(ctx.rng.gen_range(-16, 16), ctx.rng.gen_range(-16, 16)); self.structures.push(Structure { - kind: StructureKind::House, - bounds: Aabr { - min: house_pos - Vec2::new(rng.gen_range(4, 6), rng.gen_range(4, 6)), - max: house_pos + Vec2::new(rng.gen_range(4, 6), rng.gen_range(4, 6)), - }, + kind: StructureKind::House(HouseBuilding::generate(ctx.rng, Vec3::new( + house_pos.x, + house_pos.y, + ctx.sim + .and_then(|sim| sim.get_alt_approx(self.origin + house_pos)) + .unwrap_or(0.0) + .ceil() as i32, + ))), }); } // Fields let farmland = self.farms.insert(Farm { base_tile }); for _ in 0..FIELDS_PER_FARM { - self.place_field(farmland, base_tile, rng); + self.place_field(farmland, base_tile, ctx.rng); } } } @@ -451,19 +460,32 @@ impl Settlement { } } } + + // Apply structures + for structure in &self.structures { + match &structure.kind { + StructureKind::House(b) => { + let centre = b.bounds_2d().center(); + let bounds = b.bounds(); + for x in bounds.min.x..bounds.max.x { + for y in bounds.min.y..bounds.max.y { + for z in bounds.min.z..bounds.max.z { + let rpos = Vec3::new(x, y, z); + let wpos = Vec3::from(self.origin) + rpos; + let coffs = wpos - Vec3::from(wpos2d); + + if let Some(block) = b.sample(rpos) { + vol.set(coffs, block); + } + } + } + } + }, + } + } } pub fn get_color(&self, pos: Vec2) -> Option> { - if let Some(structure) = self - .structures - .iter() - .find(|s| s.bounds.contains_point(pos)) - { - return Some(match structure.kind { - StructureKind::House => Rgb::new(200, 80, 50), - }); - } - let sample = self.land.get_at_block(pos); match sample.tower {