mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Innumerable minor improvements to towns, added bridges, better paths, more house variations, etc.
This commit is contained in:
parent
8499143f77
commit
68c5612692
@ -8,6 +8,7 @@ use std::{
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use vek::*;
|
||||
use rand::prelude::*;
|
||||
use rand_chacha::ChaChaRng;
|
||||
use common::{
|
||||
terrain::TerrainChunkSize,
|
||||
vol::RectVolSize,
|
||||
@ -19,6 +20,7 @@ use common::{
|
||||
use crate::{
|
||||
sim::{WorldSim, SimChunk},
|
||||
site::{Site as WorldSite, Settlement},
|
||||
util::seed_expan,
|
||||
};
|
||||
|
||||
const CARDINALS: [Vec2<i32>; 4] = [
|
||||
@ -64,7 +66,7 @@ pub struct GenCtx<'a, R: Rng> {
|
||||
impl Civs {
|
||||
pub fn generate(seed: u32, sim: &mut WorldSim) -> Self {
|
||||
let mut this = Self::default();
|
||||
let mut rng = sim.rng.clone();
|
||||
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
|
||||
let mut ctx = GenCtx { sim, rng: &mut rng };
|
||||
|
||||
for _ in 0..INITIAL_CIV_COUNT {
|
||||
@ -89,9 +91,32 @@ impl Civs {
|
||||
|
||||
// Place sites in world
|
||||
for site in this.sites.iter() {
|
||||
let radius = 48i32;
|
||||
|
||||
let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32);
|
||||
let nearby_chunks = Spiral2d::new().map(|offs| site.center + offs).take(radius.pow(2) as usize);
|
||||
|
||||
// Flatten ground
|
||||
let flatten_radius = 12.0;
|
||||
if let Some(center_alt) = ctx.sim.get_alt_approx(wpos) {
|
||||
for pos in nearby_chunks.clone() {
|
||||
let factor = (1.0 - (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius) * 1.3;
|
||||
ctx.sim
|
||||
.get_mut(pos)
|
||||
// Don't disrupt chunks that are near water
|
||||
.filter(|chunk| !chunk.river.near_water())
|
||||
.map(|chunk| {
|
||||
let diff = Lerp::lerp_precise(chunk.alt, center_alt, factor) - chunk.alt;
|
||||
chunk.alt += diff;
|
||||
chunk.basement += diff;
|
||||
chunk.rockiness = 0.0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let settlement = WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng));
|
||||
for pos in Spiral2d::new().map(|offs| site.center + offs).take(32usize.pow(2)) {
|
||||
|
||||
for pos in nearby_chunks {
|
||||
ctx.sim
|
||||
.get_mut(pos)
|
||||
.map(|chunk| chunk.sites.push(settlement.clone()));
|
||||
|
@ -592,7 +592,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
let near_cliffs = sim_chunk.near_cliffs;
|
||||
|
||||
let river_gouge = 0.5;
|
||||
let (in_water, alt_, water_level, warp_factor) = if let Some((
|
||||
let (in_water, water_dist, alt_, water_level, warp_factor) = if let Some((
|
||||
max_border_river_pos,
|
||||
river_chunk,
|
||||
max_border_river,
|
||||
@ -603,7 +603,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
//
|
||||
// If we are <= water_alt, we are in the lake; otherwise, we are flowing into
|
||||
// it.
|
||||
let (in_water, new_alt, new_water_alt, warp_factor) = max_border_river
|
||||
let (in_water, water_dist, new_alt, new_water_alt, warp_factor) = max_border_river
|
||||
.river_kind
|
||||
.and_then(|river_kind| {
|
||||
if let RiverKind::River { cross_section } = river_kind {
|
||||
@ -623,6 +623,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
|
||||
Some((
|
||||
true,
|
||||
Some(river_dist as f32),
|
||||
Lerp::lerp(
|
||||
new_alt - cross_section.y.max(1.0),
|
||||
new_alt - 1.0,
|
||||
@ -672,6 +673,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
let _river_height_factor = river_dist / (river_width * 0.5);
|
||||
return Some((
|
||||
true,
|
||||
Some(river_dist as f32),
|
||||
alt_for_river.min(lake_water_alt - 1.0 - river_gouge),
|
||||
lake_water_alt - river_gouge,
|
||||
0.0,
|
||||
@ -680,6 +682,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
|
||||
Some((
|
||||
river_scale_factor <= 1.0,
|
||||
Some(wposf.distance(river_pos) as f32),
|
||||
alt_for_river,
|
||||
downhill_water_alt,
|
||||
river_scale_factor as f32,
|
||||
@ -711,6 +714,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
|| downhill_water_alt
|
||||
.max(river_chunk.water_alt)
|
||||
> alt_for_river,
|
||||
None,
|
||||
alt_for_river,
|
||||
(downhill_water_alt.max(river_chunk.water_alt)
|
||||
- river_gouge),
|
||||
@ -720,6 +724,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
} else {
|
||||
return Some((
|
||||
false,
|
||||
Some(lake_dist as f32),
|
||||
alt_for_river,
|
||||
downhill_water_alt,
|
||||
river_scale_factor as f32,
|
||||
@ -738,6 +743,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
if dist == Vec2::zero() {
|
||||
return Some((
|
||||
true,
|
||||
Some(lake_dist as f32),
|
||||
alt_for_river.min(lake_water_alt - 1.0 - river_gouge),
|
||||
lake_water_alt - river_gouge,
|
||||
0.0,
|
||||
@ -756,6 +762,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
if gouge_factor == 1.0 {
|
||||
return Some((
|
||||
true,
|
||||
None,
|
||||
alt.min(lake_water_alt - 1.0 - river_gouge),
|
||||
downhill_water_alt.max(lake_water_alt)
|
||||
- river_gouge,
|
||||
@ -764,6 +771,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
} else {
|
||||
return Some((
|
||||
true,
|
||||
None,
|
||||
alt_for_river,
|
||||
if in_bounds_ {
|
||||
downhill_water_alt.max(lake_water_alt)
|
||||
@ -777,15 +785,21 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
}
|
||||
Some((
|
||||
river_scale_factor <= 1.0,
|
||||
Some(lake_dist as f32),
|
||||
alt_for_river,
|
||||
downhill_water_alt,
|
||||
river_scale_factor as f32,
|
||||
))
|
||||
},
|
||||
RiverKind::River { .. } => {
|
||||
let (_, _, _, (_, (river_pos, _), _)) =
|
||||
max_border_river_dist.unwrap();
|
||||
let river_dist = wposf.distance(river_pos);
|
||||
|
||||
// FIXME: Make water altitude accurate.
|
||||
Some((
|
||||
river_scale_factor <= 1.0,
|
||||
Some(river_dist as f32),
|
||||
alt_for_river,
|
||||
downhill_water_alt,
|
||||
river_scale_factor as f32,
|
||||
@ -795,19 +809,21 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
})
|
||||
.unwrap_or((
|
||||
false,
|
||||
None,
|
||||
alt_for_river,
|
||||
downhill_water_alt,
|
||||
river_scale_factor as f32,
|
||||
))
|
||||
});
|
||||
(in_water, new_alt, new_water_alt, warp_factor)
|
||||
(in_water, water_dist, new_alt, new_water_alt, warp_factor)
|
||||
} else {
|
||||
(false, alt_for_river, downhill_water_alt, 1.0)
|
||||
(false, None, alt_for_river, downhill_water_alt, 1.0)
|
||||
};
|
||||
// NOTE: To disable warp, uncomment this line.
|
||||
// let warp_factor = 0.0;
|
||||
|
||||
let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor);
|
||||
let riverless_alt = alt + riverless_alt_delta;
|
||||
let alt = alt_ + riverless_alt_delta;
|
||||
let basement =
|
||||
alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?;
|
||||
@ -1062,6 +1078,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
|
||||
Some(ColumnSample {
|
||||
alt,
|
||||
riverless_alt,
|
||||
basement,
|
||||
chaos,
|
||||
water_level,
|
||||
@ -1096,6 +1113,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
humidity,
|
||||
spawn_rate,
|
||||
stone_col,
|
||||
water_dist,
|
||||
|
||||
chunk: sim_chunk,
|
||||
})
|
||||
@ -1105,6 +1123,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
#[derive(Clone)]
|
||||
pub struct ColumnSample<'a> {
|
||||
pub alt: f32,
|
||||
pub riverless_alt: f32,
|
||||
pub basement: f32,
|
||||
pub chaos: f32,
|
||||
pub water_level: f32,
|
||||
@ -1127,6 +1146,7 @@ pub struct ColumnSample<'a> {
|
||||
pub humidity: f32,
|
||||
pub spawn_rate: f32,
|
||||
pub stone_col: Rgb<u8>,
|
||||
pub water_dist: Option<f32>,
|
||||
|
||||
pub chunk: &'a SimChunk,
|
||||
}
|
||||
|
@ -100,9 +100,10 @@ impl World {
|
||||
let mut sampler = self.sample_blocks();
|
||||
|
||||
let chunk_wpos2d = Vec2::from(chunk_pos) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
|
||||
let grid_border = 4;
|
||||
let zcache_grid =
|
||||
Grid::populate_from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32), |offs| {
|
||||
sampler.get_z_cache(chunk_wpos2d + offs)
|
||||
Grid::populate_from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2, |offs| {
|
||||
sampler.get_z_cache(chunk_wpos2d - grid_border + offs)
|
||||
});
|
||||
|
||||
let mut chunk = TerrainChunk::new(base_z, stone, air, meta);
|
||||
@ -115,7 +116,7 @@ impl World {
|
||||
let offs = Vec2::new(x, y);
|
||||
let wpos2d = chunk_wpos2d + offs;
|
||||
|
||||
let z_cache = match zcache_grid.get(offs) {
|
||||
let z_cache = match zcache_grid.get(offs + grid_border) {
|
||||
Some(Some(z_cache)) => z_cache,
|
||||
_ => continue,
|
||||
};
|
||||
@ -146,7 +147,7 @@ impl World {
|
||||
chunk_wpos2d,
|
||||
|offs| {
|
||||
zcache_grid
|
||||
.get(offs)
|
||||
.get(grid_border + offs)
|
||||
.map(Option::as_ref)
|
||||
.flatten()
|
||||
.map(|zc| &zc.sample)
|
||||
|
@ -217,6 +217,10 @@ impl RiverData {
|
||||
}
|
||||
|
||||
pub fn near_river(&self) -> bool { self.is_river() || self.neighbor_rivers.len() > 0 }
|
||||
|
||||
pub fn near_water(&self) -> bool {
|
||||
self.near_river() || self.is_lake() || self.is_ocean()
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw rivers and assign them heights, widths, and velocities. Take some
|
||||
|
@ -1567,8 +1567,8 @@ impl WorldSim {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_alt_approx(&self, pos: Vec2<i32>) -> Option<f32> {
|
||||
self.get_interpolated(pos, |chunk| chunk.alt)
|
||||
pub fn get_alt_approx(&self, wpos: Vec2<i32>) -> Option<f32> {
|
||||
self.get_interpolated(wpos, |chunk| chunk.alt)
|
||||
}
|
||||
|
||||
pub fn get_wpos(&self, wpos: Vec2<i32>) -> Option<&SimChunk> {
|
||||
|
@ -13,6 +13,9 @@ use super::{
|
||||
pub struct House {
|
||||
roof_color: Rgb<u8>,
|
||||
noise: RandomField,
|
||||
roof_ribbing: bool,
|
||||
central_supports: bool,
|
||||
chimney: Option<i32>,
|
||||
}
|
||||
|
||||
impl Archetype for House {
|
||||
@ -26,6 +29,9 @@ impl Archetype for House {
|
||||
rng.gen_range(50, 200),
|
||||
),
|
||||
noise: RandomField::new(rng.gen()),
|
||||
roof_ribbing: rng.gen(),
|
||||
central_supports: rng.gen(),
|
||||
chimney: if rng.gen() { Some(rng.gen_range(1, 6)) } else { None },
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,15 +62,20 @@ impl Archetype for House {
|
||||
let foundation_height = 0 - (dist - width - 1).max(0);
|
||||
let roof_height = 8 + width;
|
||||
|
||||
if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y > foundation_height + 1 { // Chimney shaft
|
||||
return empty;
|
||||
}
|
||||
|
||||
if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < roof_height + 2 { // Chimney
|
||||
if center_offset.product() == 0 && profile.y > foundation_height + 1 && profile.y <= foundation_height + 3 { // Fireplace
|
||||
if let Some(chimney_height) = self.chimney {
|
||||
// Chimney shaft
|
||||
if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y > foundation_height + 1 {
|
||||
return empty;
|
||||
} else {
|
||||
return foundation;
|
||||
}
|
||||
|
||||
// Chimney
|
||||
if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < roof_height + chimney_height {
|
||||
// Fireplace
|
||||
if center_offset.product() == 0 && profile.y > foundation_height + 1 && profile.y <= foundation_height + 3 {
|
||||
return empty;
|
||||
} else {
|
||||
return foundation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,7 +100,8 @@ impl Archetype for House {
|
||||
&& profile.y >= ceil_height
|
||||
&& dist <= width + 2
|
||||
{
|
||||
if profile.x == 0 || dist == width + 2 || (roof_height - profile.y) % 3 == 0 { // Eaves
|
||||
let is_ribbing = (roof_height - profile.y) % 3 == 0 && self.roof_ribbing;
|
||||
if profile.x == 0 || dist == width + 2 || is_ribbing { // Eaves
|
||||
return log;
|
||||
} else {
|
||||
return roof;
|
||||
@ -125,7 +137,11 @@ impl Archetype for House {
|
||||
}
|
||||
|
||||
// Wall
|
||||
return if bound_offset.x == bound_offset.y || profile.x == 0 || profile.y == ceil_height { // Support beams
|
||||
return if
|
||||
bound_offset.x == bound_offset.y ||
|
||||
(profile.x == 0 && self.central_supports) ||
|
||||
profile.y == ceil_height
|
||||
{ // Support beams
|
||||
log
|
||||
} else {
|
||||
wall
|
||||
|
@ -11,8 +11,8 @@ use common::{
|
||||
astar::Astar,
|
||||
path::Path,
|
||||
spiral::Spiral2d,
|
||||
terrain::{Block, BlockKind},
|
||||
vol::{BaseVol, RectSizedVol, WriteVol, Vox},
|
||||
terrain::{Block, BlockKind, TerrainChunkSize},
|
||||
vol::{BaseVol, RectSizedVol, RectVolSize, WriteVol, Vox},
|
||||
store::{Id, Store},
|
||||
};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
@ -71,9 +71,18 @@ pub fn center_of(p: [Vec2<f32>; 3]) -> Vec2<f32> {
|
||||
Vec2::new(x, y)
|
||||
}
|
||||
|
||||
impl SimChunk {
|
||||
fn can_host_settlement(&self) -> bool {
|
||||
!self.near_cliffs && !self.river.is_river() && !self.river.is_lake()
|
||||
impl WorldSim {
|
||||
fn can_host_settlement(&self, pos: Vec2<i32>) -> bool {
|
||||
self
|
||||
.get(pos)
|
||||
.map(|chunk| {
|
||||
chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake()
|
||||
})
|
||||
.unwrap_or(false)
|
||||
&& self
|
||||
.get_gradient_approx(pos)
|
||||
.map(|grad| grad < 0.75)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,6 +112,7 @@ pub struct Settlement {
|
||||
farms: Store<Farm>,
|
||||
structures: Vec<Structure>,
|
||||
town: Option<Town>,
|
||||
noise: RandomField,
|
||||
}
|
||||
|
||||
pub struct Town {
|
||||
@ -127,6 +137,7 @@ impl Settlement {
|
||||
farms: Store::default(),
|
||||
structures: Vec::new(),
|
||||
town: None,
|
||||
noise: RandomField::new(ctx.rng.gen()),
|
||||
};
|
||||
|
||||
if let Some(sim) = ctx.sim {
|
||||
@ -138,6 +149,7 @@ impl Settlement {
|
||||
this.place_farms(&mut ctx);
|
||||
this.place_town(&mut ctx);
|
||||
this.place_paths(ctx.rng);
|
||||
this.place_buildings(&mut ctx);
|
||||
|
||||
this
|
||||
}
|
||||
@ -155,10 +167,12 @@ impl Settlement {
|
||||
.map(|x| (0..4).map(move |y| Vec2::new(x, y)))
|
||||
.flatten()
|
||||
.any(|offs| {
|
||||
sim.get_wpos(wpos + offs * AREA_SIZE as i32 / 2)
|
||||
.map(|chunk| !chunk.can_host_settlement())
|
||||
.unwrap_or(true)
|
||||
let wpos = wpos + offs * AREA_SIZE as i32 / 2;
|
||||
let cpos = wpos.map(|e| e.div_euclid(TerrainChunkSize::RECT_SIZE.x as i32));
|
||||
sim
|
||||
.can_host_settlement(cpos)
|
||||
})
|
||||
|| rng.gen_range(0, 16) == 0 // Randomly consider some tiles inaccessible
|
||||
{
|
||||
self.land.set(tile, hazard);
|
||||
}
|
||||
@ -252,50 +266,7 @@ impl Settlement {
|
||||
.plot_at_mut(base_tile)
|
||||
.map(|plot| *plot = Plot::Town);
|
||||
|
||||
for _ in 0..ctx.rng.gen_range(10, 30) {
|
||||
for _ in 0..10 {
|
||||
let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2)
|
||||
+ Vec2::<i32>::zero().map(|_| ctx.rng.gen_range(-(AREA_SIZE as i32) * 3, AREA_SIZE as i32 * 3));
|
||||
|
||||
if let Some(Plot::Town) = self.land
|
||||
.plot_at(house_pos.map(|e| e.div_euclid(AREA_SIZE as i32)))
|
||||
{} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
let structure = Structure {
|
||||
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,
|
||||
))),
|
||||
};
|
||||
|
||||
let bounds = structure.bounds_2d();
|
||||
|
||||
// Check for collision with other structures
|
||||
if self.structures
|
||||
.iter()
|
||||
.any(|s| s.bounds_2d().collides_with_aabr(bounds))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
self.structures.push(structure);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
/*
|
||||
for dir in CARDINALS.iter() {
|
||||
self.land.set(base_tile + *dir, town);
|
||||
}
|
||||
*/
|
||||
|
||||
self.town = Some(Town { base_tile });
|
||||
origin = base_tile;
|
||||
}
|
||||
@ -307,6 +278,7 @@ impl Settlement {
|
||||
.iter()
|
||||
.filter_map(|dir| {
|
||||
self.land.find_tile_dir(origin, *dir, |plot| match plot {
|
||||
Some(Plot::Hazard) => false,
|
||||
Some(Plot::Town) => false,
|
||||
_ => true,
|
||||
})
|
||||
@ -318,8 +290,8 @@ impl Settlement {
|
||||
.find_path(spokes[i], spokes[(i + 1) % spokes.len()], |_, to| match to
|
||||
.map(|to| self.land.plot(to.plot))
|
||||
{
|
||||
Some(Plot::Hazard) => 10000.0,
|
||||
Some(Plot::Town) => 1000.0,
|
||||
Some(Plot::Hazard) => 100000.0,
|
||||
Some(Plot::Town) => 10000.0,
|
||||
_ => 1.0,
|
||||
})
|
||||
.map(|path| wall_path.extend(path.iter().copied()));
|
||||
@ -346,6 +318,54 @@ impl Settlement {
|
||||
.write_path(&wall_path, WayKind::Wall, buildable, true);
|
||||
}
|
||||
|
||||
pub fn place_buildings(&mut self, ctx: &mut GenCtx<impl Rng>) {
|
||||
let town_center = if let Some(town) = self.town.as_ref() {
|
||||
town.base_tile
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
for tile in Spiral2d::new().map(|offs| town_center + offs).take(16usize.pow(2)) {
|
||||
for _ in 0..ctx.rng.gen_range(1, 5) {
|
||||
for _ in 0..10 {
|
||||
let house_pos = tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2)
|
||||
+ Vec2::<i32>::zero().map(|_| ctx.rng.gen_range(-(AREA_SIZE as i32) / 2, AREA_SIZE as i32) / 2);
|
||||
|
||||
let tile_pos = house_pos.map(|e| e.div_euclid(AREA_SIZE as i32));
|
||||
if !matches!(self.land.plot_at(tile_pos), Some(Plot::Town))
|
||||
|| self.land.tile_at(tile_pos).map(|t| t.contains(WayKind::Path)).unwrap_or(true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let structure = Structure {
|
||||
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,
|
||||
))),
|
||||
};
|
||||
|
||||
let bounds = structure.bounds_2d();
|
||||
|
||||
// Check for collision with other structures
|
||||
if self.structures
|
||||
.iter()
|
||||
.any(|s| s.bounds_2d().collides_with_aabr(bounds))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
self.structures.push(structure);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn place_farms(&mut self, ctx: &mut GenCtx<impl Rng>) {
|
||||
const FARM_COUNT: usize = 6;
|
||||
const FIELDS_PER_FARM: usize = 5;
|
||||
@ -416,7 +436,11 @@ impl Settlement {
|
||||
|
||||
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
|
||||
SpawnRules {
|
||||
trees: self.land.get_at_block(wpos - self.origin).plot.is_none(),
|
||||
trees: self.land
|
||||
.get_at_block(wpos - self.origin)
|
||||
.plot
|
||||
.map(|p| if let Plot::Hazard = p { true } else { false })
|
||||
.unwrap_or(true),
|
||||
}
|
||||
}
|
||||
|
||||
@ -441,23 +465,58 @@ impl Settlement {
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let surface_z = col_sample.alt.floor() as i32;
|
||||
let surface_z = col_sample.riverless_alt.floor() as i32;
|
||||
|
||||
// Sample settlement
|
||||
let sample = self.land.get_at_block(rpos);
|
||||
|
||||
// Ground color
|
||||
if let Some(color) = self.get_color(rpos) {
|
||||
let noisy_color = |col: Rgb<u8>, factor: u32| {
|
||||
let nz = self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, surface_z));
|
||||
col.map(|e| (e as u32 + nz % (factor * 2)).saturating_sub(factor).min(255) as u8)
|
||||
};
|
||||
|
||||
// Paths
|
||||
if let Some((WayKind::Path, dist, nearest)) = sample.way {
|
||||
let inset = -1;
|
||||
|
||||
// Try to use the column at the centre of the path for sampling to make them flatter
|
||||
let col = get_column(offs + (nearest.floor().map(|e| e as i32) - rpos)).unwrap_or(col_sample);
|
||||
let bridge_offset = if let Some(water_dist) = col.water_dist {
|
||||
((water_dist.abs() * 0.15).min(f32::consts::PI).cos() + 1.0) * 5.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let surface_z = (col.riverless_alt + bridge_offset).floor() as i32;
|
||||
|
||||
for z in inset - 2..inset {
|
||||
vol.set(
|
||||
Vec3::new(offs.x, offs.y, surface_z + z),
|
||||
if bridge_offset >= 2.0 {
|
||||
Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8))
|
||||
} else {
|
||||
Block::new(BlockKind::Normal, noisy_color(Rgb::new(90, 70, 50), 8))
|
||||
},
|
||||
);
|
||||
}
|
||||
let head_space = 6;//(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(),
|
||||
);
|
||||
}
|
||||
// Ground colour
|
||||
} else if let Some(color) = self.get_color(rpos) {
|
||||
for z in -3..5 {
|
||||
vol.set(
|
||||
Vec3::new(offs.x, offs.y, surface_z + z),
|
||||
if z >= 0 { Block::empty() } else { Block::new(BlockKind::Normal, color) },
|
||||
if z >= 0 { Block::empty() } else { Block::new(BlockKind::Normal, noisy_color(color, 4)) },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Walls
|
||||
if let Some((WayKind::Wall, dist)) = sample.way {
|
||||
if let Some((WayKind::Wall, dist, _)) = sample.way {
|
||||
let color = Lerp::lerp(
|
||||
Rgb::new(130i32, 100, 0),
|
||||
Rgb::new(90, 70, 50),
|
||||
@ -485,24 +544,6 @@ impl Settlement {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -550,9 +591,9 @@ impl Settlement {
|
||||
}
|
||||
|
||||
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)),
|
||||
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)),
|
||||
_ => {},
|
||||
}
|
||||
|
||||
@ -560,11 +601,7 @@ impl Settlement {
|
||||
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::Town) => return Some(Rgb::new(130, 120, 80)),
|
||||
Some(Plot::Field { seed, .. }) => {
|
||||
let furrow_dirs = [
|
||||
Vec2::new(1, 0),
|
||||
@ -651,7 +688,7 @@ impl Tile {
|
||||
#[derive(Default)]
|
||||
pub struct Sample<'a> {
|
||||
plot: Option<&'a Plot>,
|
||||
way: Option<(&'a WayKind, f32)>,
|
||||
way: Option<(&'a WayKind, f32, Vec2<f32>)>,
|
||||
tower: Option<(&'a Tower, Vec2<i32>)>,
|
||||
}
|
||||
|
||||
@ -690,14 +727,15 @@ impl Land {
|
||||
|
||||
for (i, dir) in CARDINALS.iter().enumerate() {
|
||||
let map = [1, 5, 7, 3];
|
||||
let line = [
|
||||
neighbors[4].0.map(|e| e as f32),
|
||||
neighbors[map[i]].0.map(|e| e as f32),
|
||||
];
|
||||
let line = LineSegment2 {
|
||||
start: neighbors[4].0.map(|e| e as f32),
|
||||
end: neighbors[map[i]].0.map(|e| e as f32),
|
||||
};
|
||||
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 proj_point = line.projected_point(pos.map(|e| e as f32));
|
||||
let dist = proj_point.distance(pos.map(|e| e as f32));
|
||||
if dist < way.width() {
|
||||
sample.way = Some((way, dist));
|
||||
sample.way = Some((way, dist, proj_point));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user