Better castle variation, improved paths, multi-storey houses

This commit is contained in:
Joshua Barretto 2020-07-03 00:14:07 +01:00
parent 6a41f6aa88
commit bdb39b8e7f
9 changed files with 253 additions and 130 deletions

View File

@ -11,6 +11,11 @@ pub struct Spiral2d {
impl Spiral2d {
#[allow(clippy::new_without_default)] // TODO: Pending review in #587
pub fn new() -> Self { Self { layer: 0, i: 0 } }
pub fn radius(self, radius: i32) -> impl Iterator<Item = Vec2<i32>> {
self.take((radius * 2 + 1).pow(2) as usize)
.filter(move |pos| pos.magnitude_squared() < (radius + 1).pow(2))
}
}
impl Iterator for Spiral2d {

View File

@ -1,7 +1,9 @@
use crate::{
all::ForestKind,
block::StructureMeta,
sim::{local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, WorldSim, Path},
sim::{
local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, Path, RiverKind, SimChunk, WorldSim,
},
util::Sampler,
Index, CONFIG,
};
@ -196,6 +198,7 @@ where
let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
let alt = sim.get_interpolated_monotone(wpos, |chunk| chunk.alt)?;
let surface_veg = sim.get_interpolated_monotone(wpos, |chunk| chunk.surface_veg)?;
let chunk_warp_factor = sim.get_interpolated_monotone(wpos, |chunk| chunk.warp_factor)?;
let sim_chunk = sim.get(chunk_pos)?;
let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
@ -860,8 +863,8 @@ where
let cold_stone = Rgb::new(0.57, 0.67, 0.8);
let hot_stone = Rgb::new(0.07, 0.07, 0.06);
let warm_stone = Rgb::new(0.77, 0.77, 0.64);
let beach_sand = Rgb::new(0.9, 0.82, 0.6);
let desert_sand = Rgb::new(0.95, 0.75, 0.5);
let beach_sand = Rgb::new(0.8, 0.75, 0.5);
let desert_sand = Rgb::new(0.7, 0.4, 0.25);
let snow = Rgb::new(0.8, 0.85, 1.0);
let stone_col = Rgb::new(195, 187, 201);
@ -1088,11 +1091,15 @@ where
water_level,
warp_factor,
surface_color: Rgb::lerp(
Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)),
// Land
ground,
// Beach
((ocean_level - 1.0) / 2.0).max(0.0),
sub_surface_color,
Rgb::lerp(
Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)),
// Land
ground,
// Beach
((ocean_level - 1.0) / 2.0).max(0.0),
),
surface_veg,
),
sub_surface_color,
// No growing directly on bedrock.

View File

@ -37,7 +37,9 @@ pub fn apply_paths_to<'a>(
})
};
if let Some((path_dist, path_nearest, path, _)) = col_sample.path.filter(|(dist, _, path, _)| *dist < path.width)
if let Some((path_dist, path_nearest, path, _)) = col_sample
.path
.filter(|(dist, _, path, _)| *dist < path.width)
{
let inset = 0;
@ -75,9 +77,9 @@ pub fn apply_paths_to<'a>(
if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 {
Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8))
} else {
let path_color = col_sample
.sub_surface_color
.map(|e| (e * 255.0 * 0.7) as u8);
let path_color = path.surface_color(
col_sample.sub_surface_color.map(|e| (e * 255.0) as u8),
);
Block::new(BlockKind::Normal, noisy_color(path_color, 8))
},
);

View File

@ -1699,8 +1699,8 @@ impl WorldSim {
Some(z0 + z1 + z2 + z3)
}
/// Return the distance to the nearest path in blocks, along with the closest point on the path
/// and the tangent vector of that path.
/// Return the distance to the nearest path in blocks, along with the closest point on the
/// path, the path metadata, and the tangent vector of that path.
pub fn get_nearest_path(&self, wpos: Vec2<i32>) -> Option<(f32, Vec2<f32>, Path, Vec2<f32>)> {
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
e.div_euclid(sz as i32)
@ -1761,7 +1761,9 @@ impl WorldSim {
.clamped(0.0, 1.0);
let pos = bez.evaluate(nearest_interval);
let dist_sqrd = pos.distance_squared(wpos.map(|e| e as f32));
Some((dist_sqrd, pos, chunk.path.path, move || bez.evaluate_derivative(nearest_interval).normalized()))
Some((dist_sqrd, pos, chunk.path.path, move || {
bez.evaluate_derivative(nearest_interval).normalized()
}))
}),
)
})
@ -1789,6 +1791,7 @@ pub struct SimChunk {
pub spawn_rate: f32,
pub river: RiverData,
pub warp_factor: f32,
pub surface_veg: f32,
pub sites: Vec<Id<Site>>,
pub place: Option<Id<Place>>,
@ -2024,6 +2027,7 @@ impl SimChunk {
spawn_rate: 1.0,
river,
warp_factor: 1.0,
surface_veg: 1.0,
sites: Vec::new(),
place: None,

View File

@ -2,7 +2,7 @@ use vek::*;
#[derive(Copy, Clone, Debug)]
pub struct Path {
pub width: f32,
pub width: f32, // Actually radius
}
#[derive(Debug)]
@ -15,23 +15,27 @@ pub struct PathData {
impl PathData {
pub fn is_path(&self) -> bool { self.neighbors != 0 }
pub fn clear(&mut self) { self.neighbors = 0; }
}
impl Default for PathData {
fn default() -> Self {
Self {
offset: Vec2::zero(),
path: Path {
width: 5.0,
},
path: Path { width: 5.0 },
neighbors: 0,
}
}
}
impl Path {
/// Return the number of blocks of headspace required at the given path distance
/// Return the number of blocks of headspace required at the given path
/// distance
pub fn head_space(&self, dist: f32) -> i32 {
(8 - (dist * 0.25).powf(6.0).round() as i32).max(1)
}
/// Get the surface colour of a path given the surrounding surface color
pub fn surface_color(&self, col: Rgb<u8>) -> Rgb<u8> { col.map(|e| (e as f32 * 0.7) as u8) }
}

View File

@ -5,7 +5,7 @@ use crate::{
sim::WorldSim,
site::{
settlement::building::{
archetype::keep::{Attr, Keep},
archetype::keep::{Attr, Keep as KeepArchetype},
Archetype, Branch, Ori,
},
BlockMask,
@ -18,6 +18,7 @@ use common::{
comp,
generation::{ChunkSupplement, EntityInfo},
npc,
spiral::Spiral2d,
store::{Id, Store},
terrain::{Block, BlockKind, Structure, TerrainChunkSize},
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
@ -29,11 +30,12 @@ use rand::prelude::*;
use std::sync::Arc;
use vek::*;
struct Segment {
struct Keep {
offset: Vec2<i32>,
locus: i32,
height: i32,
is_tower: bool,
alt: i32,
}
struct Tower {
@ -47,29 +49,44 @@ pub struct Castle {
seed: u32,
radius: i32,
towers: Vec<Tower>,
segments: Vec<Segment>,
keeps: Vec<Keep>,
rounded_towers: bool,
}
pub struct GenCtx<'a, R: Rng> {
sim: Option<&'a WorldSim>,
sim: Option<&'a mut WorldSim>,
rng: &'a mut R,
}
impl Castle {
#[allow(clippy::let_and_return)] // TODO: Pending review in #587
pub fn generate(wpos: Vec2<i32>, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self {
pub fn generate(wpos: Vec2<i32>, sim: Option<&mut WorldSim>, rng: &mut impl Rng) -> Self {
let mut ctx = GenCtx { sim, rng };
let boundary_towers = ctx.rng.gen_range(5, 10);
let keep_count = ctx.rng.gen_range(1, 4);
let boundary_noise = ctx.rng.gen_range(-2i32, 8).max(1) as f32;
let radius = 150;
// Adjust ground
if let Some(sim) = ctx.sim.as_mut() {
for rpos in Spiral2d::new()
.radius((radius as f32 * 0.7) as i32 / TerrainChunkSize::RECT_SIZE.x as i32)
{
sim.get_mut(wpos / TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + rpos)
.map(|chunk| {
chunk.surface_veg = 0.0;
chunk.path.clear();
});
}
}
let this = Self {
origin: wpos,
alt: ctx
.sim
.as_ref()
.and_then(|sim| sim.get_alt_approx(wpos))
.unwrap_or(0.0) as i32
+ 6,
@ -87,6 +104,7 @@ impl Castle {
for i in (1..80).step_by(5) {
if ctx
.sim
.as_ref()
.and_then(|sim| sim.get_nearest_path(wpos + offset))
.map(|(dist, _, _, _)| dist > 24.0)
.unwrap_or(true)
@ -101,6 +119,7 @@ impl Castle {
offset,
alt: ctx
.sim
.as_ref()
.and_then(|sim| sim.get_alt_approx(wpos + offset))
.unwrap_or(0.0) as i32
+ 2,
@ -108,19 +127,28 @@ impl Castle {
})
.collect(),
rounded_towers: ctx.rng.gen(),
keeps: (0..keep_count)
.map(|i| {
let angle = (i as f32 / keep_count as f32) * f32::consts::PI * 2.0;
let dir = Vec2::new(angle.cos(), angle.sin());
let dist =
(radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0) * 0.3;
segments: (0..0) //rng.gen_range(18, 24))
.map(|_| {
let dir = Vec2::new(ctx.rng.gen_range(-1.0, 1.0), ctx.rng.gen_range(-1.0, 1.0))
.normalized();
let dist = 16.0 + ctx.rng.gen_range(0.0f32, 1.0).powf(0.5) * 64.0;
let height = 48.0 - (dist / 64.0).powf(2.0) * 32.0;
let locus = ctx.rng.gen_range(20, 26);
let offset = (dir * dist).map(|e| e as i32);
let height = ctx.rng.gen_range(25, 70).clamped(30, 48);
Segment {
offset: (dir * dist).map(|e| e as i32),
locus: ctx.rng.gen_range(6, 26),
height: height as i32,
is_tower: height > 36.0,
Keep {
offset,
locus,
height,
is_tower: true,
alt: ctx
.sim
.as_ref()
.and_then(|sim| sim.get_alt_approx(wpos + offset))
.unwrap_or(0.0) as i32
+ 2,
}
})
.collect(),
@ -209,7 +237,8 @@ impl Castle {
} else {
rpos.yx()
};
let head_space = col_sample.path
let head_space = col_sample
.path
.map(|(dist, _, path, _)| path.head_space(dist))
.unwrap_or(0);
@ -220,34 +249,37 @@ impl Castle {
col_sample
};
let keep_archetype = KeepArchetype {
flag_color: Rgb::new(200, 80, 40),
};
for z in -10..64 {
let wpos = Vec3::new(wpos2d.x, wpos2d.y, col_sample.alt as i32 + z);
let keep = Keep {
flag_color: Rgb::new(200, 80, 40),
};
// Boundary
// Boundary wall
let wall_z = wpos.z - wall_alt;
let mut mask = if z < head_space {
BlockMask::nothing()
} else {
keep.draw(
Vec3::from(wall_rpos) + Vec3::unit_z() * wpos.z - wall_alt,
wall_dist,
Vec2::new(border_pos.reduce_max(), border_pos.reduce_min()),
rpos - wall_pos,
wall_z,
wall_ori,
4,
0,
&Attr {
height: 16,
is_tower: false,
rounded: true,
},
)
};
if z < head_space {
continue;
}
let mut mask = keep_archetype.draw(
Vec3::from(wall_rpos) + Vec3::unit_z() * wpos.z - wall_alt,
wall_dist,
Vec2::new(border_pos.reduce_max(), border_pos.reduce_min()),
rpos - wall_pos,
wall_z,
wall_ori,
4,
0,
&Attr {
height: 16,
is_tower: false,
ridged: false,
rounded: true,
},
);
// Boundary towers
for tower in &self.towers {
let tower_wpos = Vec3::new(
self.origin.x + tower.offset.x,
@ -257,7 +289,7 @@ impl Castle {
let tower_locus = 10;
let border_pos = (tower_wpos - wpos).xy().map(|e| e.abs());
mask = mask.resolve_with(keep.draw(
mask = mask.resolve_with(keep_archetype.draw(
if (tower_wpos.x - wpos.x).abs() < (tower_wpos.y - wpos.y).abs() {
wpos - tower_wpos
} else {
@ -277,6 +309,42 @@ impl Castle {
&Attr {
height: 28,
is_tower: true,
ridged: false,
rounded: self.rounded_towers,
},
));
}
// Keeps
for keep in &self.keeps {
let keep_wpos = Vec3::new(
self.origin.x + keep.offset.x,
self.origin.y + keep.offset.y,
keep.alt,
);
let border_pos = (keep_wpos - wpos).xy().map(|e| e.abs());
mask = mask.resolve_with(keep_archetype.draw(
if (keep_wpos.x - wpos.x).abs() < (keep_wpos.y - wpos.y).abs() {
wpos - keep_wpos
} else {
Vec3::new(
wpos.y - keep_wpos.y,
wpos.x - keep_wpos.x,
wpos.z - keep_wpos.z,
)
},
border_pos.reduce_max() - keep.locus,
Vec2::new(border_pos.reduce_max(), border_pos.reduce_min()),
(wpos - keep_wpos).xy(),
wpos.z - keep.alt,
Ori::East,
keep.locus,
0,
&Attr {
height: keep.height,
is_tower: keep.is_tower,
ridged: true,
rounded: self.rounded_towers,
},
));

View File

@ -105,7 +105,7 @@ impl Attr {
},
mansard: rng.gen_range(-7, 4).max(0),
pillar: match rng.gen_range(0, 4) {
0 => Pillar::Chimney(rng.gen_range(1, 5)),
0 => Pillar::Chimney(rng.gen_range(2, 6)),
_ => Pillar::None,
},
levels: rng.gen_range(1, 3),
@ -135,7 +135,7 @@ impl Archetype for House {
storey_fill: StoreyFill::All,
mansard: 0,
pillar: match rng.gen_range(0, 3) {
0 => Pillar::Chimney(rng.gen_range(1, 5)),
0 => Pillar::Chimney(rng.gen_range(2, 6)),
1 => Pillar::Tower(5 + rng.gen_range(1, 5)),
_ => Pillar::None,
},
@ -409,7 +409,7 @@ impl Archetype for House {
// Window
if (frame_bounds.size() + 1).reduce_min() > 2 {
// Window frame is large enough for a window
let surface_pos = Vec2::new(bound_offset.x, profile.y - floor_height);
let surface_pos = Vec2::new(bound_offset.x, profile.y);
if window_bounds.contains_point(surface_pos) {
return Some(end_window);
} else if frame_bounds.contains_point(surface_pos) {

View File

@ -14,6 +14,7 @@ pub struct Keep {
pub struct Attr {
pub height: i32,
pub is_tower: bool,
pub ridged: bool,
pub rounded: bool,
}
@ -30,6 +31,7 @@ impl Archetype for Keep {
attr: Attr {
height: rng.gen_range(12, 16),
is_tower: false,
ridged: false,
rounded: true,
},
locus: 10 + rng.gen_range(0, 5),
@ -43,6 +45,7 @@ impl Archetype for Keep {
attr: Attr {
height: rng.gen_range(20, 28),
is_tower: true,
ridged: false,
rounded: true,
},
locus: 4 + rng.gen_range(0, 5),
@ -104,15 +107,21 @@ impl Archetype for Keep {
let internal = BlockMask::new(Block::empty(), internal_layer);
let empty = BlockMask::nothing();
let width = locus;
let rampart_width = 2 + locus;
let ceil_height = attr.height;
let door_height = 6;
let edge_pos = if (bound_offset.x == rampart_width) ^ (ori == Ori::East) {
let edge_pos = if (bound_offset.x.abs() > bound_offset.y.abs()) ^ (ori == Ori::East) {
pos.y
} else {
pos.x
};
let width = locus
+ if edge_pos % 4 == 0 && attr.ridged && !attr.rounded {
1
} else {
0
};
let rampart_width = 2 + width;
let ceil_height = attr.height;
let door_height = 6;
let rampart_height = ceil_height + if edge_pos % 2 == 0 { 3 } else { 4 };
let min_dist = if attr.rounded {
bound_offset.map(|e| e.pow(2) as f32).sum().powf(0.5) as i32

View File

@ -593,14 +593,15 @@ impl Settlement {
// 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
// // 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, depth) = if let Some(water_dist) = col.water_dist {
// (
// ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0,
// ((1.0 - ((water_dist + 2.0) * 0.3).min(0.0).cos().abs())
// ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) *
// 5.0, ((1.0 - ((water_dist + 2.0) *
// 0.3).min(0.0).cos().abs())
// * (col.riverless_alt + 5.0 - col.alt).max(0.0)
// * 1.75
// + 3.0) as i32,
@ -614,10 +615,10 @@ impl Settlement {
// let _ = vol.set(
// Vec3::new(offs.x, offs.y, surface_z + z),
// if bridge_offset >= 2.0 && dist >= 3.0 || z < inset - 1 {
// Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8))
// } else {
// Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 50, 30), 8))
// },
// Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80,
// 100), 8)) } else {
// Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 50,
// 30), 8)) },
// );
// }
// let head_space = (8 - (dist * 0.25).powf(6.0).round() as i32).max(1);
@ -639,6 +640,7 @@ impl Settlement {
Some(Plot::Dirt) => Some(Rgb::new(90, 70, 50)),
Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)),
Some(Plot::Water) => Some(Rgb::new(100, 150, 250)),
Some(Plot::Town { district }) => None,
Some(Plot::Town { district }) => {
if let Some((_, path_nearest, _, _)) = col_sample.path {
let path_dir = (path_nearest - wpos2d.map(|e| e as f32))
@ -669,65 +671,87 @@ impl Settlement {
}))
},
Some(Plot::Field { seed, crop, .. }) => {
let furrow_dirs = [
Vec2::new(1, 0),
Vec2::new(0, 1),
Vec2::new(1, 1),
Vec2::new(-1, 1),
];
let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()];
let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2;
if let Some(color) = col_sample.path.and_then(|(dist, _, path, _)| {
if dist < path.width {
Some(path.surface_color(
col_sample.sub_surface_color.map(|e| (e * 255.0) as u8),
))
} else {
None
}
}) {
Some(color)
} else {
let furrow_dirs = [
Vec2::new(1, 0),
Vec2::new(0, 1),
Vec2::new(1, 1),
Vec2::new(-1, 1),
];
let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()];
let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2;
let dirt = Rgb::new(80, 55, 35).map(|e| {
e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32)) % 32)
as u8
});
let mound =
Rgb::new(70, 80, 30).map(|e| e + roll(0, 8) as u8).map(|e| {
e + (self.noise.get(Vec3::broadcast((seed % 4096 + 1) as i32))
let dirt = Rgb::new(80, 55, 35).map(|e| {
e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32))
% 32) as u8
});
let mound =
Rgb::new(70, 80, 30).map(|e| e + roll(0, 8) as u8).map(|e| {
e + (self
.noise
.get(Vec3::broadcast((seed % 4096 + 1) as i32))
% 32) as u8
});
if in_furrow {
if roll(0, 5) == 0 {
surface_block = match crop {
Crop::Corn => Some(BlockKind::Corn),
Crop::Wheat if roll(1, 2) == 0 => {
Some(BlockKind::WheatYellow)
},
Crop::Wheat => Some(BlockKind::WheatGreen),
Crop::Cabbage if roll(2, 2) == 0 => {
Some(BlockKind::Cabbage)
},
Crop::Pumpkin if roll(3, 2) == 0 => {
Some(BlockKind::Pumpkin)
},
Crop::Flax if roll(4, 2) == 0 => Some(BlockKind::Flax),
Crop::Carrot if roll(5, 2) == 0 => Some(BlockKind::Carrot),
Crop::Tomato if roll(6, 2) == 0 => Some(BlockKind::Tomato),
Crop::Radish if roll(7, 2) == 0 => Some(BlockKind::Radish),
Crop::Turnip if roll(8, 2) == 0 => Some(BlockKind::Turnip),
Crop::Sunflower => Some(BlockKind::Sunflower),
_ => None,
}
.or_else(|| {
if roll(9, 400) == 0 {
Some(BlockKind::Scarecrow)
} else {
None
if in_furrow {
if roll(0, 5) == 0 {
surface_block = match crop {
Crop::Corn => Some(BlockKind::Corn),
Crop::Wheat if roll(1, 2) == 0 => {
Some(BlockKind::WheatYellow)
},
Crop::Wheat => Some(BlockKind::WheatGreen),
Crop::Cabbage if roll(2, 2) == 0 => {
Some(BlockKind::Cabbage)
},
Crop::Pumpkin if roll(3, 2) == 0 => {
Some(BlockKind::Pumpkin)
},
Crop::Flax if roll(4, 2) == 0 => Some(BlockKind::Flax),
Crop::Carrot if roll(5, 2) == 0 => {
Some(BlockKind::Carrot)
},
Crop::Tomato if roll(6, 2) == 0 => {
Some(BlockKind::Tomato)
},
Crop::Radish if roll(7, 2) == 0 => {
Some(BlockKind::Radish)
},
Crop::Turnip if roll(8, 2) == 0 => {
Some(BlockKind::Turnip)
},
Crop::Sunflower => Some(BlockKind::Sunflower),
_ => None,
}
})
.map(|kind| Block::new(kind, Rgb::white()));
.or_else(|| {
if roll(9, 400) == 0 {
Some(BlockKind::Scarecrow)
} else {
None
}
})
.map(|kind| Block::new(kind, Rgb::white()));
}
} else if roll(0, 20) == 0 {
surface_block =
Some(Block::new(BlockKind::ShortGrass, Rgb::white()));
} else if roll(1, 30) == 0 {
surface_block =
Some(Block::new(BlockKind::MediumGrass, Rgb::white()));
}
} else if roll(0, 20) == 0 {
surface_block =
Some(Block::new(BlockKind::ShortGrass, Rgb::white()));
} else if roll(1, 30) == 0 {
surface_block =
Some(Block::new(BlockKind::MediumGrass, Rgb::white()));
}
Some(if in_furrow { dirt } else { mound })
Some(if in_furrow { dirt } else { mound })
}
},
_ => None,
};