Start of layered settlement generation, better settlement terraingen

This commit is contained in:
Joshua Barretto 2020-04-01 21:38:01 +01:00
parent 714346ef83
commit 589229f668
5 changed files with 233 additions and 170 deletions

View File

@ -1,6 +1,6 @@
use rand::thread_rng; use rand::thread_rng;
use vek::*; use vek::*;
use veloren_world::generator::settlement::Settlement; use veloren_world::site::Settlement;
const W: usize = 640; const W: usize = 640;
const H: usize = 480; const H: usize = 480;

View File

@ -39,7 +39,7 @@ fn attempt<T>(max_iters: usize, mut f: impl FnMut() -> Option<T>) -> Option<T> {
(0..max_iters).find_map(|_| f()) (0..max_iters).find_map(|_| f())
} }
const INITIAL_CIV_COUNT: usize = 16; const INITIAL_CIV_COUNT: usize = 32;
#[derive(Default)] #[derive(Default)]
pub struct Civs { pub struct Civs {
@ -216,7 +216,10 @@ impl Civs {
labors: MapVec::from_default(0.01), labors: MapVec::from_default(0.01),
yields: MapVec::from_default(1.0), yields: MapVec::from_default(1.0),
productivity: MapVec::from_default(1.0),
last_exports: Stocks::from_default(0.0),
export_targets: Stocks::from_default(0.0),
trade_states: Stocks::default(), trade_states: Stocks::default(),
coin: 1000.0, coin: 1000.0,
}); });
@ -262,67 +265,64 @@ impl Civs {
} }
// Trade stocks // Trade stocks
let mut stocks = TRADE_STOCKS; // let mut stocks = TRADE_STOCKS;
stocks.shuffle(ctx.rng); // Give each stock a chance to be traded first // stocks.shuffle(ctx.rng); // Give each stock a chance to be traded first
for stock in stocks.iter().copied() { // for stock in stocks.iter().copied() {
let mut sell_orders = self.sites // let mut sell_orders = self.sites
.iter_ids() // .iter_ids()
.map(|(id, site)| (id, { // .map(|(id, site)| (id, {
let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::<f32>(); // econ::SellOrder {
let quantity = if let Some(val) = site.values[stock] { // quantity: site.export_targets[stock].max(0.0).min(site.stocks[stock]),
((total_value / val) * site.surplus[stock].max(0.0)).min(site.stocks[stock]) // price: site.trade_states[stock].sell_belief.choose_price(ctx) * 1.25, // Trade cost
} else { // q_sold: 0.0,
0.0 // }
}; // }))
econ::SellOrder { // .filter(|(_, order)| order.quantity > 0.0)
quantity, // .collect::<Vec<_>>();
price: site.trade_states[stock].sell_belief.choose_price(ctx) * 1.25, // Trade cost
q_sold: 0.0,
}
}))
.filter(|(_, order)| order.quantity > 0.0)
.collect::<Vec<_>>();
let mut sites = self.sites // let mut sites = self.sites
.ids() // .ids()
.collect::<Vec<_>>(); // .collect::<Vec<_>>();
sites.shuffle(ctx.rng); // Give all sites a chance to buy first // sites.shuffle(ctx.rng); // Give all sites a chance to buy first
for site in sites { // for site in sites {
let (max_spend, max_price) = { // let (max_spend, max_price, max_import) = {
let site = self.sites.get(site); // let site = self.sites.get(site);
let budget = site.coin * 0.5; // let budget = site.coin * 0.5;
let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::<f32>(); // let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::<f32>();
( // (
(site.values[stock].unwrap_or(0.1) / total_value * budget).min(budget), // 100000.0,//(site.values[stock].unwrap_or(0.1) / total_value * budget).min(budget),
site.trade_states[stock].buy_belief.price, // site.trade_states[stock].buy_belief.price,
) // -site.export_targets[stock].min(0.0),
}; // )
let (quantity, spent) = econ::buy_units(ctx, sell_orders // };
.iter_mut() // let (quantity, spent) = econ::buy_units(ctx, sell_orders
.filter(|(id, _)| site != *id && self.track_between(site, *id).is_some()) // .iter_mut()
.map(|(_, order)| order), // .filter(|(id, _)| site != *id && self.track_between(site, *id).is_some())
1000000.0, // Max quantity TODO // .map(|(_, order)| order),
1000000.0, // Max price TODO // max_import,
max_spend, // 1000000.0, // Max price TODO
); // max_spend,
let mut site = self.sites.get_mut(site); // );
site.coin -= spent; // let mut site = self.sites.get_mut(site);
if quantity > 0.0 { // site.coin -= spent;
site.stocks[stock] += quantity; // if quantity > 0.0 {
site.trade_states[stock].buy_belief.update_buyer(years, spent / quantity); // site.stocks[stock] += quantity;
println!("Belief: {:?}", site.trade_states[stock].buy_belief); // site.last_exports[stock] = -quantity;
} // site.trade_states[stock].buy_belief.update_buyer(years, spent / quantity);
} // println!("Belief: {:?}", site.trade_states[stock].buy_belief);
// }
// }
for (site, order) in sell_orders { // for (site, order) in sell_orders {
let mut site = self.sites.get_mut(site); // let mut site = self.sites.get_mut(site);
site.coin += order.q_sold * order.price; // site.coin += order.q_sold * order.price;
if order.q_sold > 0.0 { // if order.q_sold > 0.0 {
site.stocks[stock] -= order.q_sold; // site.stocks[stock] -= order.q_sold;
site.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity); // site.last_exports[stock] = order.q_sold;
} // site.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity);
} // }
} // }
// }
} }
} }
@ -472,7 +472,10 @@ pub struct Site {
labors: MapVec<Occupation, f32>, labors: MapVec<Occupation, f32>,
// Per worker, per year, of their output good // Per worker, per year, of their output good
yields: MapVec<Occupation, f32>, yields: MapVec<Occupation, f32>,
productivity: MapVec<Occupation, f32>,
last_exports: Stocks<f32>,
export_targets: Stocks<f32>,
trade_states: Stocks<TradeState>, trade_states: Stocks<TradeState>,
coin: f32, coin: f32,
} }
@ -488,14 +491,18 @@ impl fmt::Display for Site {
for (stock, q) in self.stocks.iter() { for (stock, q) in self.stocks.iter() {
writeln!(f, "- {}: {}", stock, q.floor())?; writeln!(f, "- {}: {}", stock, q.floor())?;
} }
writeln!(f, "Prices")?; writeln!(f, "Values")?;
for (stock, v) in self.values.iter() { for stock in TRADE_STOCKS.iter() {
writeln!(f, "- {}: {}", stock, v.map(|x| x.to_string()).unwrap_or_else(|| "N/A".to_string()))?; writeln!(f, "- {}: {}", stock, self.values[*stock].map(|x| x.to_string()).unwrap_or_else(|| "N/A".to_string()))?;
} }
writeln!(f, "Laborers")?; writeln!(f, "Laborers")?;
for (labor, n) in self.labors.iter() { for (labor, n) in self.labors.iter() {
writeln!(f, "- {}: {}", labor, (*n * self.population).floor() as u32)?; writeln!(f, "- {}: {}", labor, (*n * self.population).floor() as u32)?;
} }
writeln!(f, "Export targets")?;
for (stock, n) in self.export_targets.iter() {
writeln!(f, "- {}: {}", stock, n)?;
}
Ok(()) Ok(())
} }
@ -526,8 +533,8 @@ impl Site {
} }
let orders = vec![ let orders = vec![
(None, vec![(FOOD, 0.25)]), (None, vec![(FOOD, 0.5)]),
(Some(COOK), vec![(FLOUR, 6.5), (MEAT, 1.5)]), (Some(COOK), vec![(FLOUR, 16.0), (MEAT, 4.0), (WOOD, 3.0)]),
(Some(LUMBERJACK), vec![(LOGS, 4.5)]), (Some(LUMBERJACK), vec![(LOGS, 4.5)]),
(Some(MINER), vec![(ROCK, 7.5)]), (Some(MINER), vec![(ROCK, 7.5)]),
(Some(FISHER), vec![(FISH, 4.0)]), (Some(FISHER), vec![(FISH, 4.0)]),
@ -537,20 +544,33 @@ impl Site {
.into_iter() .into_iter()
.collect::<HashMap<_, Vec<(Stock, f32)>>>(); .collect::<HashMap<_, Vec<(Stock, f32)>>>();
// Per labourer, per year
let production = Stocks::from_list(&[
(FARMER, (FLOUR, 2.0)),
(LUMBERJACK, (WOOD, 1.5)),
(MINER, (STONE, 0.6)),
(FISHER, (MEAT, 3.0)),
(HUNTER, (MEAT, 0.25)),
(COOK, (FOOD, 20.0)),
]);
let mut demand = Stocks::from_default(0.0); let mut demand = Stocks::from_default(0.0);
for (labor, orders) in &orders { for (labor, orders) in &orders {
let scale = if let Some(labor) = labor { self.labors[*labor] } else { 1.0 } * self.population; let scale = if let Some(labor) = labor { self.labors[*labor] } else { 1.0 } * self.population;
for (stock, amount) in orders { for (stock, amount) in orders {
debug_assert!(!amount.is_nan(), "{:?}, {}", labor, stock);
debug_assert!(!scale.is_nan(), "{:?}, {}, {}", labor, stock, self.population);
demand[*stock] += *amount * scale; demand[*stock] += *amount * scale;
} }
} }
let mut supply = Stocks::from_default(0.0);
for (labor, (output_stock, _)) in production.iter() {
supply[*output_stock] += self.yields[labor] * self.labors[labor] * self.population;
}
let last_exports = &self.last_exports;
let stocks = &self.stocks;
self.surplus = demand.clone().map(|stock, tgt| { self.surplus = demand.clone().map(|stock, tgt| {
debug_assert!(!self.stocks[stock].is_nan()); supply[stock] + stocks[stock] - demand[stock] - last_exports[stock]
debug_assert!(!demand[stock].is_nan());
self.stocks[stock] - demand[stock]
}); });
// Update values according to the surplus of each stock // Update values according to the surplus of each stock
@ -560,30 +580,28 @@ impl Site {
values[stock] = if val > 0.001 && val < 1000.0 { Some(val) } else { None }; values[stock] = if val > 0.001 && val < 1000.0 { Some(val) } else { None };
}); });
// Per labourer, per year // Update export targets based on relative values
let production = Stocks::from_list(&[ let value_avg =
(FARMER, (FLOUR, 2.0)), values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::<f32>().max(0.01)
(LUMBERJACK, (WOOD, 1.5)), / values.iter().filter(|(_, v)| v.is_some()).count() as f32;
(MINER, (STONE, 0.6)), let export_targets = &mut self.export_targets;
(FISHER, (MEAT, 3.0)), let last_exports = &self.last_exports;
(HUNTER, (MEAT, 0.5)), let trade_states = &self.trade_states;
(COOK, (FOOD, 8.0)), self.values.iter().for_each(|(stock, value)| {
]); let rvalue = (*value).map(|v| v - value_avg).unwrap_or(0.0);
//let factor = if export_targets[stock] > 0.0 { 1.0 / rvalue } else { rvalue };
export_targets[stock] = last_exports[stock] - rvalue * 0.1;// + (trade_states[stock].sell_belief.price - trade_states[stock].buy_belief.price) * 0.025;
});
let population = self.population; let population = self.population;
// Redistribute workforce according to relative good values // Redistribute workforce according to relative good values
let labor_ratios = production.clone().map(|labor, (output_stock, _)| { let labor_ratios = production.clone().map(|labor, (output_stock, _)| {
debug_assert!(self.values[output_stock].unwrap_or(0.0) < 1000000.0, "{:?}", self.values[output_stock]); self.productivity[labor] * demand[output_stock] / supply[output_stock].max(0.001)
debug_assert!(self.yields[labor] < 1000000.0, "{}", self.yields[labor]);
self.values[output_stock].unwrap_or(0.0) * self.yields[labor]
}); });
let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::<f32>().max(0.01); let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::<f32>().max(0.01);
assert!(labor_ratio_sum > 0.0);
production.iter().for_each(|(labor, _)| { production.iter().for_each(|(labor, _)| {
debug_assert!(!labor_ratios[labor].is_nan() && !labor_ratios[labor].is_infinite(), "{:?}, {}", labor, labor_ratios[labor]); let smooth = 0.8;
debug_assert!(!labor_ratio_sum.is_nan() && !labor_ratio_sum.is_infinite(), "{:?}, {}", labor, labor_ratio_sum);
let smooth = 0.5;
self.labors[labor] = smooth * self.labors[labor] + (1.0 - smooth) * (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum); self.labors[labor] = smooth * self.labors[labor] + (1.0 - smooth) * (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum);
}); });
@ -596,7 +614,8 @@ impl Site {
// we can produce! For example, if we need 0.25 fish and 0.75 oats to make 1 unit of // we can produce! For example, if we need 0.25 fish and 0.75 oats to make 1 unit of
// food, but only 0.5 units of oats are available then we only need to consume 2/3rds // food, but only 0.5 units of oats are available then we only need to consume 2/3rds
// of other ingredients and leave the rest in stock // of other ingredients and leave the rest in stock
let min_satisfaction = orders // In effect, this is the productivity
let productivity = orders
.iter() .iter()
.map(|(stock, amount)| { .map(|(stock, amount)| {
// What quantity is this order requesting? // What quantity is this order requesting?
@ -612,7 +631,7 @@ impl Site {
// What quantity is this order requesting? // What quantity is this order requesting?
let quantity = *amount * scale; let quantity = *amount * scale;
// What amount gets actually used in production? // What amount gets actually used in production?
let used = quantity * min_satisfaction; let used = quantity * productivity;
// Deplete stocks accordingly // Deplete stocks accordingly
self.stocks[*stock] = (self.stocks[*stock] - used).max(0.0); self.stocks[*stock] = (self.stocks[*stock] - used).max(0.0);
@ -621,9 +640,11 @@ impl Site {
// Industries produce things // Industries produce things
if let Some(labor) = labor { if let Some(labor) = labor {
let (stock, rate) = production[*labor]; let (stock, rate) = production[*labor];
let yield_per_worker = min_satisfaction * rate;
self.yields[*labor] = yield_per_worker;
let workers = self.labors[*labor] * population; let workers = self.labors[*labor] * population;
let final_rate = rate;
let yield_per_worker = productivity * final_rate;
self.yields[*labor] = yield_per_worker;
self.productivity[*labor] = productivity;
self.stocks[stock] += yield_per_worker * workers.powf(1.1); self.stocks[stock] += yield_per_worker * workers.powf(1.1);
} }
} }
@ -634,8 +655,6 @@ impl Site {
// Births/deaths // Births/deaths
const NATURAL_BIRTH_RATE: f32 = 0.15; const NATURAL_BIRTH_RATE: f32 = 0.15;
const DEATH_RATE: f32 = 0.05; const DEATH_RATE: f32 = 0.05;
debug_assert!(!self.surplus[FOOD].is_nan());
debug_assert!(!self.surplus[FOOD].is_infinite());
let birth_rate = if self.surplus[FOOD] > 0.0 { NATURAL_BIRTH_RATE } else { 0.0 }; let birth_rate = if self.surplus[FOOD] > 0.0 { NATURAL_BIRTH_RATE } else { 0.0 };
self.population += years * self.population * (birth_rate - DEATH_RATE); self.population += years * self.population * (birth_rate - DEATH_RATE);
} }
@ -752,7 +771,3 @@ impl<K: Copy + Eq + Hash, T: Default + Clone> std::ops::IndexMut<K> for MapVec<K
} }

View File

@ -999,13 +999,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
humidity.sub(CONFIG.jungle_hum).mul(1.0), humidity.sub(CONFIG.jungle_hum).mul(1.0),
); );
let ground = sim_chunk.sites.iter().fold(ground, |ground, site| {
site.get_surface(wpos)
.and_then(|block| block.get_color())
.map(|col| col.map(|e| e as f32 / 255.0))
.unwrap_or(ground)
});
// Snow covering // Snow covering
let snow_cover = temp let snow_cover = temp
.sub(CONFIG.snow_temp) .sub(CONFIG.snow_temp)
@ -1082,7 +1075,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
), ),
sub_surface_color, sub_surface_color,
// No growing directly on bedrock. // No growing directly on bedrock.
tree_density: Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5)), // And, no growing on sites that don't want them TODO: More precise than this when we apply trees as a post-processing layer
tree_density: if sim_chunk.sites.iter().all(|site| site.spawn_rules(wpos).trees) {
Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5))
} else {
0.0
},
forest_kind: sim_chunk.forest_kind, forest_kind: sim_chunk.forest_kind,
close_structures: self.gen_close_structures(wpos), close_structures: self.gen_close_structures(wpos),
cave_xy, cave_xy,

View File

@ -14,6 +14,10 @@ use common::{
use std::{fmt, sync::Arc}; use std::{fmt, sync::Arc};
use vek::*; use vek::*;
pub struct SpawnRules {
pub trees: bool,
}
#[derive(Clone)] #[derive(Clone)]
pub enum Site { pub enum Site {
Settlement(Arc<Settlement>), Settlement(Arc<Settlement>),
@ -26,9 +30,9 @@ impl Site {
} }
} }
pub fn get_surface(&self, wpos: Vec2<i32>) -> Option<Block> { pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
match self { match self {
Site::Settlement(settlement) => settlement.get_surface(wpos), Site::Settlement(s) => s.spawn_rules(wpos)
} }
} }

View File

@ -3,12 +3,13 @@ use crate::{
sim::{SimChunk, WorldSim}, sim::{SimChunk, WorldSim},
util::{Grid, RandomField, Sampler, StructureGen2d}, util::{Grid, RandomField, Sampler, StructureGen2d},
}; };
use super::SpawnRules;
use common::{ use common::{
astar::Astar, astar::Astar,
path::Path, path::Path,
spiral::Spiral2d, spiral::Spiral2d,
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
vol::{BaseVol, RectSizedVol, WriteVol}, vol::{BaseVol, RectSizedVol, WriteVol, Vox},
store::{Id, Store}, store::{Id, Store},
}; };
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
@ -357,6 +358,12 @@ impl Settlement {
pub fn radius(&self) -> f32 { 1200.0 } pub fn radius(&self) -> f32 { 1200.0 }
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
SpawnRules {
trees: self.land.get_at_block(wpos - self.origin).plot.is_none(),
}
}
pub fn apply_to<'a>( pub fn apply_to<'a>(
&'a self, &'a self,
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
@ -369,52 +376,81 @@ impl Settlement {
for x in 0..vol.size_xy().x as i32 { for x in 0..vol.size_xy().x as i32 {
let offs = Vec2::new(x, y); let offs = Vec2::new(x, y);
let wpos2d = wpos2d + offs;
let rpos = wpos2d - self.origin;
// Sample terrain
let col_sample = if let Some(col_sample) = get_column(offs) { let col_sample = if let Some(col_sample) = get_column(offs) {
col_sample col_sample
} else { } else {
continue; continue;
}; };
let surface_z = col_sample.alt.floor() as i32;
let wpos2d = wpos2d + offs; // Sample settlement
let sample = self.land.get_at_block(rpos);
match self.land.get_at_block(wpos2d - self.origin) { // Ground color
Sample::Way(WayKind::Wall, dist) => { if let Some(color) = self.get_color(rpos) {
let color = Lerp::lerp( for z in -3..3 {
Rgb::new(130i32, 100, 0), vol.set(
Rgb::new(90, 70, 50), Vec3::new(offs.x, offs.y, surface_z + z),
(rand_field.get(wpos2d.into()) % 256) as f32 / 256.0, if z >= 0 { Block::empty() } else { Block::new(BlockKind::Normal, color) },
) );
.map(|e| (e % 256) as u8); }
for z in 0..12 { }
if dist / WayKind::Wall.width()
< ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) // Walls
{ if let Some((WayKind::Wall, dist)) = sample.way {
vol.set( let color = Lerp::lerp(
Vec3::new(offs.x, offs.y, col_sample.alt.floor() as i32 + z), Rgb::new(130i32, 100, 0),
Block::new(BlockKind::Normal, color), Rgb::new(90, 70, 50),
); (rand_field.get(wpos2d.into()) % 256) as f32 / 256.0,
} )
} .map(|e| (e % 256) as u8);
}, for z in 0..12 {
Sample::Tower(Tower::Wall, _pos) => { if dist / WayKind::Wall.width()
for z in 0..16 { < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0)
{
vol.set( vol.set(
Vec3::new(offs.x, offs.y, col_sample.alt.floor() as i32 + z), Vec3::new(offs.x, offs.y, surface_z + z),
Block::new(BlockKind::Normal, Rgb::new(50, 50, 50)), Block::new(BlockKind::Normal, color),
); );
} }
}, }
_ => {}, }
// Towers
if let Some((Tower::Wall, _pos)) = sample.tower {
for z in 0..16 {
vol.set(
Vec3::new(offs.x, offs.y, surface_z + z),
Block::new(BlockKind::Normal, Rgb::new(50, 50, 50)),
);
}
}
// Paths
if let Some((WayKind::Path, dist)) = sample.way {
let inset = -1;
for z in -3..inset {
vol.set(
Vec3::new(offs.x, offs.y, surface_z + z),
Block::new(BlockKind::Normal, Rgb::new(90, 70, 50)),
);
}
let head_space = (6 - (dist * 0.4).powf(6.0).round() as i32).max(1);
for z in inset..inset + head_space {
vol.set(
Vec3::new(offs.x, offs.y, surface_z + z),
Block::empty(),
);
}
} }
} }
} }
} }
pub fn get_surface(&self, wpos: Vec2<i32>) -> Option<Block> {
self.get_color(wpos - self.origin)
.map(|col| Block::new(BlockKind::Normal, col))
}
pub fn get_color(&self, pos: Vec2<i32>) -> Option<Rgb<u8>> { pub fn get_color(&self, pos: Vec2<i32>) -> Option<Rgb<u8>> {
if let Some(structure) = self if let Some(structure) = self
.structures .structures
@ -426,24 +462,30 @@ impl Settlement {
}); });
} }
Some(match self.land.get_at_block(pos) { let sample = self.land.get_at_block(pos);
Sample::Wilderness => return None,
Sample::Plot(Plot::Hazard) => return None, match sample.tower {
Sample::Way(WayKind::Path, _) => Rgb::new(90, 70, 50), Some((Tower::Wall, _)) => return Some(Rgb::new(50, 50, 50)),
Sample::Way(WayKind::Hedge, _) => Rgb::new(0, 150, 0), _ => {},
Sample::Way(WayKind::Wall, _) => Rgb::new(60, 60, 60), }
Sample::Tower(Tower::Wall, _) => Rgb::new(50, 50, 50),
Sample::Plot(Plot::Dirt) => Rgb::new(90, 70, 50), match sample.way {
Sample::Plot(Plot::Grass) => Rgb::new(100, 200, 0), Some((WayKind::Path, _)) => return Some(Rgb::new(90, 70, 50)),
Sample::Plot(Plot::Water) => Rgb::new(100, 150, 250), Some((WayKind::Hedge, _)) => return Some(Rgb::new(0, 150, 0)),
Sample::Plot(Plot::Town) => { Some((WayKind::Wall, _)) => return Some(Rgb::new(60, 60, 60)),
if pos.map(|e| e.rem_euclid(4) < 2).reduce(|x, y| x ^ y) { _ => {},
Rgb::new(200, 130, 120) }
} else {
Rgb::new(160, 150, 120) match sample.plot {
} Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)),
}, Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)),
Sample::Plot(Plot::Field { seed, .. }) => { Some(Plot::Water) => return Some(Rgb::new(100, 150, 250)),
Some(Plot::Town) => return Some(if pos.map(|e| e.rem_euclid(4) < 2).reduce(|x, y| x ^ y) {
Rgb::new(200, 130, 120)
} else {
Rgb::new(160, 150, 120)
}),
Some(Plot::Field { seed, .. }) => {
let furrow_dirs = [ let furrow_dirs = [
Vec2::new(1, 0), Vec2::new(1, 0),
Vec2::new(0, 1), Vec2::new(0, 1),
@ -452,7 +494,7 @@ impl Settlement {
]; ];
let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()];
let furrow = (pos * furrow_dir).sum().rem_euclid(6) < 3; let furrow = (pos * furrow_dir).sum().rem_euclid(6) < 3;
Rgb::new( return Some(Rgb::new(
if furrow { if furrow {
100 100
} else { } else {
@ -460,9 +502,12 @@ impl Settlement {
}, },
64 + seed.to_le_bytes()[1] % 128, 64 + seed.to_le_bytes()[1] % 128,
16 + seed.to_le_bytes()[2] % 32, 16 + seed.to_le_bytes()[2] % 32,
) ));
}, },
}) _ => {},
}
None
} }
} }
@ -523,11 +568,11 @@ impl Tile {
pub fn contains(&self, kind: WayKind) -> bool { self.ways.iter().any(|way| way == &Some(kind)) } pub fn contains(&self, kind: WayKind) -> bool { self.ways.iter().any(|way| way == &Some(kind)) }
} }
pub enum Sample<'a> { #[derive(Default)]
Wilderness, pub struct Sample<'a> {
Plot(&'a Plot), plot: Option<&'a Plot>,
Way(&'a WayKind, f32), way: Option<(&'a WayKind, f32)>,
Tower(&'a Tower, Vec2<i32>), tower: Option<(&'a Tower, Vec2<i32>)>,
} }
pub struct Land { pub struct Land {
@ -546,6 +591,8 @@ impl Land {
} }
pub fn get_at_block(&self, pos: Vec2<i32>) -> Sample { pub fn get_at_block(&self, pos: Vec2<i32>) -> Sample {
let mut sample = Sample::default();
let neighbors = self.sampler_warp.get(pos); let neighbors = self.sampler_warp.get(pos);
let closest = neighbors let closest = neighbors
.iter() .iter()
@ -557,7 +604,7 @@ impl Land {
if let Some(tower) = center_tile.and_then(|tile| tile.tower.as_ref()) { if let Some(tower) = center_tile.and_then(|tile| tile.tower.as_ref()) {
if (neighbors[4].0.distance_squared(pos) as f32) < tower.radius().powf(2.0) { if (neighbors[4].0.distance_squared(pos) as f32) < tower.radius().powf(2.0) {
return Sample::Tower(tower, neighbors[4].0); sample.tower = Some((tower, neighbors[4].0));
} }
} }
@ -570,15 +617,14 @@ impl Land {
if let Some(way) = center_tile.and_then(|tile| tile.ways[i].as_ref()) { if let Some(way) = center_tile.and_then(|tile| tile.ways[i].as_ref()) {
let dist = dist_to_line(line, pos.map(|e| e as f32)); let dist = dist_to_line(line, pos.map(|e| e as f32));
if dist < way.width() { if dist < way.width() {
return Sample::Way(way, dist); sample.way = Some((way, dist));
} }
} }
} }
let plot = self.plot_at(closest.map(to_tile)); sample.plot = self.plot_at(closest.map(to_tile));
plot.map(|plot| Sample::Plot(plot)) sample
.unwrap_or(Sample::Wilderness)
} }
pub fn tile_at(&self, pos: Vec2<i32>) -> Option<&Tile> { self.tiles.get(&pos) } pub fn tile_at(&self, pos: Vec2<i32>) -> Option<&Tile> { self.tiles.get(&pos) }