This commit is contained in:
Joshua Barretto 2021-05-10 13:24:20 +01:00
parent 78aa587722
commit f59ac2f8ee
10 changed files with 225 additions and 21 deletions

View File

@ -33,11 +33,16 @@ use crate::{
use rand::prelude::*;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use tracing::warn;
use std::{
convert::AsRef,
marker::PhantomData,
};
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct Lottery<T> {
items: Vec<(f32, T)>,
pub struct Lottery<T, Items: AsRef<[(f32, T)]> = Vec<(f32, T)>> {
items: Items,
total: f32,
phantom: PhantomData<T>,
}
impl<T: DeserializeOwned + Send + Sync + 'static> assets::Asset for Lottery<T> {
@ -55,15 +60,29 @@ impl<T> From<Vec<(f32, T)>> for Lottery<T> {
*rate = total - *rate;
}
Self { items, total }
Self { items, total, phantom: PhantomData }
}
}
impl<T> Lottery<T> {
impl<'a, T> Lottery<T, &'a mut [(f32, T)]> {
pub fn from_slice(items: &'a mut [(f32, T)]) -> Self {
let mut total = 0.0;
for (rate, _) in items.iter_mut() {
total += *rate;
*rate = total - *rate;
}
Self { items, total, phantom: PhantomData }
}
}
impl<T, Items: AsRef<[(f32, T)]>> Lottery<T, Items> {
pub fn choose_seeded(&self, seed: u32) -> &T {
let x = ((seed % 65536) as f32 / 65536.0) * self.total;
&self.items[self
&self.items.as_ref()[self
.items
.as_ref()
.binary_search_by(|(y, _)| y.partial_cmp(&x).unwrap())
.unwrap_or_else(|i| i.saturating_sub(1))]
.1
@ -71,7 +90,7 @@ impl<T> Lottery<T> {
pub fn choose(&self) -> &T { self.choose_seeded(thread_rng().gen()) }
pub fn iter(&self) -> impl Iterator<Item = &(f32, T)> { self.items.iter() }
pub fn iter(&self) -> impl Iterator<Item = &(f32, T)> { self.items.as_ref().iter() }
pub fn total(&self) -> f32 { self.total }
}

View File

@ -67,7 +67,7 @@ impl Fill {
let inner = Aabr {
min: aabb.min.xy() - 1 + inset,
max: aabb.max.xy() - inset,
};
}.made_valid();
aabb_contains(*aabb, pos)
&& ((inner.projected_point(pos.xy()) - pos.xy())
.map(|e| e.abs())
@ -241,7 +241,7 @@ impl Fill {
}
}
pub trait Structure {
pub trait Render {
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Id<Primitive>, Fill)>(
&self,
site: &Site,

View File

@ -1,9 +1,11 @@
mod gen;
mod planning;
mod plot;
mod structure;
mod tile;
use self::{
gen::{Fill, Primitive, Structure},
gen::{Fill, Primitive, Render},
plot::{Plot, PlotKind},
tile::{HazardKind, KeepKind, Ori, RoofKind, Tile, TileGrid, TileKind, TILE_SIZE},
};
@ -166,7 +168,7 @@ impl Site {
) -> Option<(Aabr<i32>, Vec2<i32>)> {
self.tiles.find_near(search_pos, |center, _| {
self.tiles
.grow_aabr(center, area_range.clone(), min_dims)
.grow_aabr(center, area_range.clone(), min_dims, 0)
.ok()
.filter(|aabr| {
(aabr.min.x..aabr.max.x)
@ -285,6 +287,13 @@ impl Site {
site.demarcate_obstacles(land);
for _ in 0..100 {
site.tick(land, &mut rng);
}
return site;
/*
site.make_plaza(land, &mut rng);
let build_chance = Lottery::from(vec![(128.0, 1), (5.0, 2), (8.0, 3), (0.75, 4)]);
@ -523,6 +532,7 @@ impl Site {
}
site
*/
}
pub fn wpos_tile_pos(&self, wpos2d: Vec2<i32>) -> Vec2<i32> {
@ -711,6 +721,7 @@ impl Site {
for plot in plots_to_render {
let (prim_tree, fills) = match &self.plots[plot].kind {
PlotKind::Hut(hut) => hut.render_collect(self),
PlotKind::House(house) => house.render_collect(self),
PlotKind::Castle(castle) => castle.render_collect(self),
_ => continue,

View File

@ -0,0 +1,35 @@
use common::lottery::Lottery;
use super::{
*,
structure::{Structure, Hut},
};
// All are weights, must be positive, 1.0 is default.
pub struct Values {
defence: f32,
farming: f32,
housing: f32,
}
impl Site {
// TODO: How long is a tick? A year?
pub fn tick(&mut self, land: &Land, rng: &mut impl Rng) {
let values = Values {
defence: 1.0,
farming: 1.0,
housing: 1.0,
};
match *Lottery::from_slice(&mut [
(10.0, 0), // Huts
])
.choose_seeded(rng.gen())
{
0 => {
Hut::choose_location((), land, self, rng)
.map(|hut| hut.generate(land, self, rng));
},
_ => unreachable!(),
}
}
}

View File

@ -2,6 +2,7 @@ mod castle;
mod house;
pub use self::{castle::Castle, house::House};
pub use super::structure::Hut;
use super::*;
use crate::util::DHashSet;
@ -26,6 +27,7 @@ impl Plot {
}
pub enum PlotKind {
Hut(Hut),
House(House),
Plaza,
Castle(Castle),

View File

@ -41,7 +41,7 @@ impl Castle {
}
}
impl Structure for Castle {
impl Render for Castle {
#[allow(clippy::identity_op)]
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Id<Primitive>, Fill)>(
&self,

View File

@ -52,7 +52,7 @@ impl House {
}
}
impl Structure for House {
impl Render for House {
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Id<Primitive>, Fill)>(
&self,
site: &Site,

View File

@ -0,0 +1,110 @@
use super::*;
use vek::*;
pub struct Hut {
root: Vec2<i32>,
tile_aabr: Aabr<i32>,
bounds: Aabr<i32>,
alt: i32,
height: i32,
door_dir: Vec2<i32>,
}
impl Structure for Hut {
type Config = ();
fn choose_location<R: Rng>(cfg: Self::Config, land: &Land, site: &Site, rng: &mut R) -> Option<Self> {
let (tile_aabr, root) = site.tiles.find_near(
Vec2::zero(),
|tile, _| if rng.gen_range(0..16) == 0 {
site.tiles.grow_aabr(tile, 4..9, (2, 2), 2).ok()
} else {
None
},
)?;
let center = (tile_aabr.min + (tile_aabr.max - 1)) / 2;
Some(Self {
root,
tile_aabr,
bounds: Aabr {
min: site.tile_wpos(tile_aabr.min),
max: site.tile_wpos(tile_aabr.max),
},
alt: land.get_alt_approx(site.tile_center_wpos(center)) as i32,
height: 4,
door_dir: match rng.gen_range(0..4) {
0 => Vec2::unit_x(),
1 => -Vec2::unit_x(),
2 => Vec2::unit_y(),
3 => -Vec2::unit_y(),
_ => unreachable!(),
},
})
}
fn generate<R: Rng>(self, land: &Land, site: &mut Site, rng: &mut R) {
let aabr = self.tile_aabr;
let plot = site.create_plot(Plot {
root_tile: self.root,
tiles: aabr_tiles(aabr).collect(),
seed: rng.gen(),
kind: PlotKind::Hut(self),
});
site.blit_aabr(aabr, Tile {
kind: TileKind::Building,
plot: Some(plot),
});
}
}
impl Render for Hut {
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Id<Primitive>, Fill)>(
&self,
site: &Site,
mut prim: F,
mut fill: G,
) {
let daub = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(110, 50, 16)));
let outer = prim(Primitive::Aabb(Aabb {
min: (self.bounds.min + 1).with_z(self.alt),
max: (self.bounds.max - 1).with_z(self.alt + self.height),
}));
let inner = prim(Primitive::Aabb(Aabb {
min: (self.bounds.min + 2).with_z(self.alt),
max: (self.bounds.max - 2).with_z(self.alt + self.height),
}));
let door_pos = site.tile_center_wpos(self.root);
let door = prim(Primitive::Aabb(Aabb {
min: door_pos.with_z(self.alt),
max: (door_pos + self.door_dir * 32).with_z(self.alt + 2),
}.made_valid()));
let space = prim(Primitive::And(inner, door));
let walls = prim(Primitive::AndNot(outer, space));
fill(walls, daub);
let roof_lip = 2;
let roof_height = (self.bounds.min - self.bounds.max)
.map(|e| e.abs())
.reduce_min()
.saturating_sub(1)
/ 2
+ roof_lip
+ 1;
let roof = prim(Primitive::Pyramid{
aabb: Aabb {
min: (self.bounds.min + 1 - roof_lip).with_z(self.alt + self.height),
max: (self.bounds.max - 1 + roof_lip).with_z(self.alt + self.height + roof_height),
},
inset: Vec2::broadcast(roof_height),
});
fill(roof, daub);
}
}

View File

@ -0,0 +1,17 @@
mod hut;
pub use self::{
hut::Hut,
};
use super::*;
pub trait Structure: Sized {
type Config;
/// Attempt to choose a location to place this plot in the given site.
fn choose_location<R: Rng>(cfg: Self::Config, land: &Land, site: &Site, rng: &mut R) -> Option<Self>;
/// Generate the plot with the given location information on the given site
fn generate<R: Rng>(self, land: &Land, site: &mut Site, rng: &mut R);
}

View File

@ -75,8 +75,11 @@ impl TileGrid {
&self,
center: Vec2<i32>,
area_range: Range<u32>,
min_dims: Extent2<u32>,
min_dims: impl Into<Extent2<u32>>,
radius: u32,
) -> Result<Aabr<i32>, Aabr<i32>> {
let min_dims = min_dims.into() + radius * 2;
let mut aabr = Aabr {
min: center,
max: center + 1,
@ -86,15 +89,16 @@ impl TileGrid {
return Err(aabr);
};
let inner_size = |aabr: Aabr<i32>| aabr.size().map(|e| e.saturating_sub(radius as i32 * 2));
let mut last_growth = 0;
for i in 0..32 {
if i - last_growth >= 4
|| aabr.size().product()
+ if i % 2 == 0 {
aabr.size().h
|| (inner_size(aabr) + if i % 2 == 0 {
Extent2::new(0, 1)
} else {
aabr.size().w
}
Extent2::new(1, 0)
}).product()
> area_range.end as i32
{
break;
@ -130,13 +134,19 @@ impl TileGrid {
}
}
if aabr.size().product() as u32 >= area_range.start
let inner_size = inner_size(aabr);
let inner_aabr = Aabr {
min: aabr.min + radius as i32,
max: aabr.min + radius as i32 + inner_size,
};
if inner_size.product() as u32 >= area_range.start
&& aabr.size().w as u32 >= min_dims.w
&& aabr.size().h as u32 >= min_dims.h
{
Ok(aabr)
Ok(inner_aabr)
} else {
Err(aabr)
Err(inner_aabr)
}
}