From 966f96c5889d2a469e5d777df88d9e6fe0a0fb6e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 1 Apr 2020 21:38:01 +0100 Subject: [PATCH] Start of layered settlement generation, better settlement terraingen --- world/examples/settlement_viewer.rs | 2 +- world/src/civ/mod.rs | 207 +++++++++++++++------------- world/src/column/mod.rs | 14 +- world/src/site/mod.rs | 8 +- world/src/site/settlement/mod.rs | 172 ++++++++++++++--------- 5 files changed, 233 insertions(+), 170 deletions(-) diff --git a/world/examples/settlement_viewer.rs b/world/examples/settlement_viewer.rs index ca3da4a5b4..44c392cc6e 100644 --- a/world/examples/settlement_viewer.rs +++ b/world/examples/settlement_viewer.rs @@ -1,6 +1,6 @@ use rand::thread_rng; use vek::*; -use veloren_world::generator::settlement::Settlement; +use veloren_world::site::Settlement; const W: usize = 640; const H: usize = 480; diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 1520f39ae9..298f3050d9 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -39,7 +39,7 @@ fn attempt(max_iters: usize, mut f: impl FnMut() -> Option) -> Option { (0..max_iters).find_map(|_| f()) } -const INITIAL_CIV_COUNT: usize = 16; +const INITIAL_CIV_COUNT: usize = 32; #[derive(Default)] pub struct Civs { @@ -216,7 +216,10 @@ impl Civs { labors: MapVec::from_default(0.01), 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(), coin: 1000.0, }); @@ -262,67 +265,64 @@ impl Civs { } // Trade stocks - let mut stocks = TRADE_STOCKS; - stocks.shuffle(ctx.rng); // Give each stock a chance to be traded first - for stock in stocks.iter().copied() { - let mut sell_orders = self.sites - .iter_ids() - .map(|(id, site)| (id, { - let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::(); - let quantity = if let Some(val) = site.values[stock] { - ((total_value / val) * site.surplus[stock].max(0.0)).min(site.stocks[stock]) - } else { - 0.0 - }; - econ::SellOrder { - quantity, - 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::>(); + // let mut stocks = TRADE_STOCKS; + // stocks.shuffle(ctx.rng); // Give each stock a chance to be traded first + // for stock in stocks.iter().copied() { + // let mut sell_orders = self.sites + // .iter_ids() + // .map(|(id, site)| (id, { + // econ::SellOrder { + // quantity: site.export_targets[stock].max(0.0).min(site.stocks[stock]), + // 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::>(); - let mut sites = self.sites - .ids() - .collect::>(); - sites.shuffle(ctx.rng); // Give all sites a chance to buy first - for site in sites { - let (max_spend, max_price) = { - let site = self.sites.get(site); - let budget = site.coin * 0.5; - let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::(); - ( - (site.values[stock].unwrap_or(0.1) / total_value * budget).min(budget), - site.trade_states[stock].buy_belief.price, - ) - }; - let (quantity, spent) = econ::buy_units(ctx, sell_orders - .iter_mut() - .filter(|(id, _)| site != *id && self.track_between(site, *id).is_some()) - .map(|(_, order)| order), - 1000000.0, // Max quantity TODO - 1000000.0, // Max price TODO - max_spend, - ); - let mut site = self.sites.get_mut(site); - site.coin -= spent; - if quantity > 0.0 { - site.stocks[stock] += quantity; - site.trade_states[stock].buy_belief.update_buyer(years, spent / quantity); - println!("Belief: {:?}", site.trade_states[stock].buy_belief); - } - } + // let mut sites = self.sites + // .ids() + // .collect::>(); + // sites.shuffle(ctx.rng); // Give all sites a chance to buy first + // for site in sites { + // let (max_spend, max_price, max_import) = { + // let site = self.sites.get(site); + // let budget = site.coin * 0.5; + // let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::(); + // ( + // 100000.0,//(site.values[stock].unwrap_or(0.1) / total_value * budget).min(budget), + // 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() + // .filter(|(id, _)| site != *id && self.track_between(site, *id).is_some()) + // .map(|(_, order)| order), + // max_import, + // 1000000.0, // Max price TODO + // max_spend, + // ); + // let mut site = self.sites.get_mut(site); + // site.coin -= spent; + // if quantity > 0.0 { + // site.stocks[stock] += quantity; + // 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 { - let mut site = self.sites.get_mut(site); - site.coin += order.q_sold * order.price; - if order.q_sold > 0.0 { - site.stocks[stock] -= order.q_sold; - site.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity); - } - } - } + // for (site, order) in sell_orders { + // let mut site = self.sites.get_mut(site); + // site.coin += order.q_sold * order.price; + // if order.q_sold > 0.0 { + // site.stocks[stock] -= order.q_sold; + // 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, // Per worker, per year, of their output good yields: MapVec, + productivity: MapVec, + last_exports: Stocks, + export_targets: Stocks, trade_states: Stocks, coin: f32, } @@ -488,14 +491,18 @@ impl fmt::Display for Site { for (stock, q) in self.stocks.iter() { writeln!(f, "- {}: {}", stock, q.floor())?; } - writeln!(f, "Prices")?; - for (stock, v) in self.values.iter() { - writeln!(f, "- {}: {}", stock, v.map(|x| x.to_string()).unwrap_or_else(|| "N/A".to_string()))?; + writeln!(f, "Values")?; + for stock in TRADE_STOCKS.iter() { + writeln!(f, "- {}: {}", stock, self.values[*stock].map(|x| x.to_string()).unwrap_or_else(|| "N/A".to_string()))?; } writeln!(f, "Laborers")?; for (labor, n) in self.labors.iter() { 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(()) } @@ -526,8 +533,8 @@ impl Site { } let orders = vec![ - (None, vec![(FOOD, 0.25)]), - (Some(COOK), vec![(FLOUR, 6.5), (MEAT, 1.5)]), + (None, vec![(FOOD, 0.5)]), + (Some(COOK), vec![(FLOUR, 16.0), (MEAT, 4.0), (WOOD, 3.0)]), (Some(LUMBERJACK), vec![(LOGS, 4.5)]), (Some(MINER), vec![(ROCK, 7.5)]), (Some(FISHER), vec![(FISH, 4.0)]), @@ -537,20 +544,33 @@ impl Site { .into_iter() .collect::>>(); + // 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); for (labor, orders) in &orders { let scale = if let Some(labor) = labor { self.labors[*labor] } else { 1.0 } * self.population; 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; } } + 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| { - debug_assert!(!self.stocks[stock].is_nan()); - debug_assert!(!demand[stock].is_nan()); - self.stocks[stock] - demand[stock] + supply[stock] + stocks[stock] - demand[stock] - last_exports[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 }; }); - // 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.5)), - (COOK, (FOOD, 8.0)), - ]); + // Update export targets based on relative values + let value_avg = + values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::().max(0.01) + / values.iter().filter(|(_, v)| v.is_some()).count() as f32; + let export_targets = &mut self.export_targets; + let last_exports = &self.last_exports; + let trade_states = &self.trade_states; + 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; // Redistribute workforce according to relative good values 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]); - debug_assert!(self.yields[labor] < 1000000.0, "{}", self.yields[labor]); - self.values[output_stock].unwrap_or(0.0) * self.yields[labor] + self.productivity[labor] * demand[output_stock] / supply[output_stock].max(0.001) }); let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::().max(0.01); - assert!(labor_ratio_sum > 0.0); production.iter().for_each(|(labor, _)| { - debug_assert!(!labor_ratios[labor].is_nan() && !labor_ratios[labor].is_infinite(), "{:?}, {}", labor, labor_ratios[labor]); - debug_assert!(!labor_ratio_sum.is_nan() && !labor_ratio_sum.is_infinite(), "{:?}, {}", labor, labor_ratio_sum); - let smooth = 0.5; + let smooth = 0.8; 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 // 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 - let min_satisfaction = orders + // In effect, this is the productivity + let productivity = orders .iter() .map(|(stock, amount)| { // What quantity is this order requesting? @@ -612,7 +631,7 @@ impl Site { // What quantity is this order requesting? let quantity = *amount * scale; // What amount gets actually used in production? - let used = quantity * min_satisfaction; + let used = quantity * productivity; // Deplete stocks accordingly self.stocks[*stock] = (self.stocks[*stock] - used).max(0.0); @@ -621,9 +640,11 @@ impl Site { // Industries produce things if let Some(labor) = 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 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); } } @@ -634,8 +655,6 @@ impl Site { // Births/deaths const NATURAL_BIRTH_RATE: f32 = 0.15; 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 }; self.population += years * self.population * (birth_rate - DEATH_RATE); } @@ -752,7 +771,3 @@ impl std::ops::IndexMut for MapVec Sampler<'a> for ColumnGen<'a> { 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 let snow_cover = temp .sub(CONFIG.snow_temp) @@ -1082,7 +1075,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ), sub_surface_color, // 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, close_structures: self.gen_close_structures(wpos), cave_xy, diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 1fe239851b..892078a0c3 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -14,6 +14,10 @@ use common::{ use std::{fmt, sync::Arc}; use vek::*; +pub struct SpawnRules { + pub trees: bool, +} + #[derive(Clone)] pub enum Site { Settlement(Arc), @@ -26,9 +30,9 @@ impl Site { } } - pub fn get_surface(&self, wpos: Vec2) -> Option { + pub fn spawn_rules(&self, wpos: Vec2) -> SpawnRules { match self { - Site::Settlement(settlement) => settlement.get_surface(wpos), + Site::Settlement(s) => s.spawn_rules(wpos) } } diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 92922f0324..1c43e5f965 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -3,12 +3,13 @@ use crate::{ sim::{SimChunk, WorldSim}, util::{Grid, RandomField, Sampler, StructureGen2d}, }; +use super::SpawnRules; use common::{ astar::Astar, path::Path, spiral::Spiral2d, terrain::{Block, BlockKind}, - vol::{BaseVol, RectSizedVol, WriteVol}, + vol::{BaseVol, RectSizedVol, WriteVol, Vox}, store::{Id, Store}, }; use hashbrown::{HashMap, HashSet}; @@ -357,6 +358,12 @@ impl Settlement { pub fn radius(&self) -> f32 { 1200.0 } + pub fn spawn_rules(&self, wpos: Vec2) -> SpawnRules { + SpawnRules { + trees: self.land.get_at_block(wpos - self.origin).plot.is_none(), + } + } + pub fn apply_to<'a>( &'a self, wpos2d: Vec2, @@ -369,52 +376,81 @@ impl Settlement { for x in 0..vol.size_xy().x as i32 { 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) { col_sample } else { 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) { - Sample::Way(WayKind::Wall, dist) => { - let color = Lerp::lerp( - Rgb::new(130i32, 100, 0), - 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 { - if dist / WayKind::Wall.width() - < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) - { - vol.set( - Vec3::new(offs.x, offs.y, col_sample.alt.floor() as i32 + z), - Block::new(BlockKind::Normal, color), - ); - } - } - }, - Sample::Tower(Tower::Wall, _pos) => { - for z in 0..16 { + // Ground color + if let Some(color) = self.get_color(rpos) { + for z in -3..3 { + vol.set( + Vec3::new(offs.x, offs.y, surface_z + z), + if z >= 0 { Block::empty() } else { Block::new(BlockKind::Normal, color) }, + ); + } + } + + // Walls + if let Some((WayKind::Wall, dist)) = sample.way { + let color = Lerp::lerp( + Rgb::new(130i32, 100, 0), + 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 { + if dist / WayKind::Wall.width() + < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) + { vol.set( - Vec3::new(offs.x, offs.y, col_sample.alt.floor() as i32 + z), - Block::new(BlockKind::Normal, Rgb::new(50, 50, 50)), + Vec3::new(offs.x, offs.y, surface_z + z), + 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) -> Option { - self.get_color(wpos - self.origin) - .map(|col| Block::new(BlockKind::Normal, col)) - } - pub fn get_color(&self, pos: Vec2) -> Option> { if let Some(structure) = self .structures @@ -426,24 +462,30 @@ impl Settlement { }); } - Some(match self.land.get_at_block(pos) { - Sample::Wilderness => return None, - Sample::Plot(Plot::Hazard) => return None, - Sample::Way(WayKind::Path, _) => Rgb::new(90, 70, 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), - Sample::Plot(Plot::Grass) => Rgb::new(100, 200, 0), - Sample::Plot(Plot::Water) => Rgb::new(100, 150, 250), - Sample::Plot(Plot::Town) => { - 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) - } - }, - Sample::Plot(Plot::Field { seed, .. }) => { + let sample = self.land.get_at_block(pos); + + match sample.tower { + Some((Tower::Wall, _)) => return Some(Rgb::new(50, 50, 50)), + _ => {}, + } + + match sample.way { + Some((WayKind::Path, _)) => return Some(Rgb::new(90, 70, 50)), + Some((WayKind::Hedge, _)) => return Some(Rgb::new(0, 150, 0)), + Some((WayKind::Wall, _)) => return Some(Rgb::new(60, 60, 60)), + _ => {}, + } + + match sample.plot { + Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)), + Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)), + 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 = [ Vec2::new(1, 0), Vec2::new(0, 1), @@ -452,7 +494,7 @@ impl Settlement { ]; let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; let furrow = (pos * furrow_dir).sum().rem_euclid(6) < 3; - Rgb::new( + return Some(Rgb::new( if furrow { 100 } else { @@ -460,9 +502,12 @@ impl Settlement { }, 64 + seed.to_le_bytes()[1] % 128, 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 enum Sample<'a> { - Wilderness, - Plot(&'a Plot), - Way(&'a WayKind, f32), - Tower(&'a Tower, Vec2), +#[derive(Default)] +pub struct Sample<'a> { + plot: Option<&'a Plot>, + way: Option<(&'a WayKind, f32)>, + tower: Option<(&'a Tower, Vec2)>, } pub struct Land { @@ -546,6 +591,8 @@ impl Land { } pub fn get_at_block(&self, pos: Vec2) -> Sample { + let mut sample = Sample::default(); + let neighbors = self.sampler_warp.get(pos); let closest = neighbors .iter() @@ -557,7 +604,7 @@ impl Land { 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) { - 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()) { let dist = dist_to_line(line, pos.map(|e| e as f32)); 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)) - .unwrap_or(Sample::Wilderness) + sample } pub fn tile_at(&self, pos: Vec2) -> Option<&Tile> { self.tiles.get(&pos) }