World colors are all hotloadable.

They live in assets/world/style/colors.ron.

Only a small handful of hardcoed colors remain in World; they are either
part of the map, or difficult to disentangle from the rest of the
computation.  Comments are made where appropriate.
This commit is contained in:
Joshua Yanovski 2020-08-20 04:28:38 +02:00
parent 5b1625f99d
commit e2f5162e4f
31 changed files with 894 additions and 397 deletions

View File

@ -1,3 +1,5 @@
#![enable(unwrap_newtypes)]
( (
// NOTE: You can't change the legnths of these arrays without updating num_hair_colors() in // NOTE: You can't change the legnths of these arrays without updating num_hair_colors() in
// common/src/comp/body/humanoid.rs. That's because this is a hack; we should really use enum // common/src/comp/body/humanoid.rs. That's because this is a hack; we should really use enum

View File

@ -0,0 +1,152 @@
#![enable(unwrap_newtypes)]
#![enable(implicit_some)]
// NOTE: Many of these colors are not used directly, but are modified in various ways (e.g. via
// lerping). So don't be too frustrated if a color change seems to have a different effect in
// different places; just follow the trends.
(
block: (
pyramid: (203, 170, 146),
// These are all ranges from low to high.
structure_blocks: (
None: None,
// Samples the surface color.
Grass: None,
// Water blocks ignore color information, and even if they didn't would not be lerped.
Water: None,
GreenSludge: None,
// Leaves all actually get interpolated.
TemperateLeaves: (start: (0, 132, 94), end: (142, 181, 0)),
PineLeaves: (start: (0, 60, 50), end: (30, 100, 10)),
PalmLeavesInner: (start: (61, 166, 43), end: (29, 130, 32)),
PalmLeavesOuter: (start: (62, 171, 38), end: (45, 171, 65)),
Acacia: (start: (15, 126, 50), end: (30, 180, 10)),
Liana: (start: (0, 125, 107), end: (0, 155, 129)),
Mangrove: (start: (32, 56, 22), end: (57, 69, 27)),
)
// Water blocks ignore color now so this isn't used, but just in case this color was worth
// remembering here it is.
// green_sludge: (30.0, 126.0, 23.0)
),
column: (
cold_grass: (0.0, 0.5, 0.25),
warm_grass: (0.4, 0.8, 0.0),
dark_grass: (0.15, 0.4, 0.1),
wet_grass: (0.1, 0.8, 0.2),
cold_stone: (0.57, 0.67, 0.8),
hot_stone: (0.07, 0.07, 0.06),
warm_stone: (0.77, 0.77, 0.64),
beach_sand: (0.8, 0.75, 0.5),
desert_sand: (0.7, 0.4, 0.25),
snow: (0.8, 0.85, 1.0),
stone_col: (195, 187, 201),
dirt_low: (0.075, 0.07, 0.3),
dirt_high: (0.75, 0.55, 0.1),
snow_high: (0.01, 0.3, 0.0),
warm_stone_high: (0.3, 0.12, 0.2),
grass_high: (0.15, 0.2, 0.15),
tropical_high: (0.87, 0.62, 0.56),
),
// NOTE: I think (but am not sure) that this is the color of stuff below the bottom-most
// ground. I'm not sure how easy it is to see.
deep_stone_color: (125, 120, 130),
layer: (
bridge: (80, 80, 100),
stalagtite: (200, 200, 200),
),
site: (
castle: (),
dungeon: (
stone: (150, 150, 175),
),
settlement: (
building: (
archetype: (
keep: (
brick_base: (80, 80, 80),
floor_base: (80, 60, 10),
pole: (90, 70, 50),
flag: (
Evil: (80, 10, 130),
Good: (200, 80, 40),
),
stone: (
Evil: (65, 60, 55),
Good: (100, 100, 110),
),
),
house: (
foundation: (100, 100, 100),
floor: (100, 75, 50),
roof: (
Roof1: (0x99, 0x5E, 0x54),
Roof2: (0x43, 0x63, 0x64),
Roof3: (0x76, 0x6D, 0x68),
Roof4: (0x7B, 0x41, 0x61),
Roof5: (0x52, 0x20, 0x20),
Roof6: (0x1A, 0x4A, 0x59),
Roof7: (0xCC, 0x76, 0x4E),
// (0x1D, 0x4D, 0x45),
// (0xB3, 0x7D, 0x60),
// (0xAC, 0x5D, 0x26),
// (0x32, 0x46, 0x6B),
// (0x2B, 0x19, 0x0F),
// (0x93, 0x78, 0x51),
// (0x92, 0x57, 0x24),
// (0x4A, 0x4E, 0x4E),
// (0x2F, 0x32, 0x47),
// (0x8F, 0x35, 0x43),
// (0x6D, 0x1E, 0x3A),
// (0x6D, 0xA7, 0x80),
// (0x4F, 0xA0, 0x95),
// (0xE2, 0xB9, 0x99),
// (0x7A, 0x30, 0x22),
// (0x4A, 0x06, 0x08),
// (0x8E, 0xB4, 0x57),
),
wall: (
Wall1: (200, 180, 150),
Wall2: (0xB8, 0xB4, 0xA4),
Wall3: (0x76, 0x6D, 0x68),
Wall4: (0xF3, 0xC9, 0x8F),
Wall5: (0xD3, 0xB7, 0x99),
Wall6: (0xE1, 0xAB, 0x91),
Wall7: (0x82, 0x57, 0x4C),
Wall8: (0xB9, 0x96, 0x77),
Wall9: (0xAE, 0x8D, 0x9C),
),
support: (
Support1: (60, 45, 30),
Support2: (0x65, 0x55, 0x56),
Support3: (0x53, 0x33, 0x13),
Support4: (0x58, 0x42, 0x33),
),
),
),
),
plot_town_path: (100, 95, 65),
plot_field_dirt: (80, 55, 35),
plot_field_mound: (70, 80, 30),
wall_low: (130, 100, 0),
wall_high :(90, 70, 50),
tower_color: (50, 50, 50),
// NOTE: Ideally these would be part of a make_case_elim, but we can't use it beacuse
// it doesn't support struct variants yet.
plot_dirt: (90, 70, 50),
plot_grass: (100, 200, 0),
plot_water: (100, 150, 250),
plot_town: (150, 110, 60),
// TODO: Add field furrow stuff.
),
),
)

View File

@ -170,5 +170,13 @@ impl ReloadIndicator {
} }
// Returns true if the watched file was changed // Returns true if the watched file was changed
pub fn reloaded(&self) -> bool { self.reloaded.swap(false, Ordering::Acquire) } pub fn reloaded(&self) -> bool {
// Optimize for the common case by performing an initial relaxed read, avoiding
// the atomic write.
if self.reloaded.load(Ordering::Relaxed) {
self.reloaded.swap(false, Ordering::Acquire)
} else {
false
}
}
} }

View File

@ -4,6 +4,7 @@
#![feature( #![feature(
arbitrary_enum_discriminant, arbitrary_enum_discriminant,
const_checked_int_methods, const_checked_int_methods,
fundamental,
option_unwrap_none, option_unwrap_none,
bool_to_option, bool_to_option,
label_break_value, label_break_value,
@ -38,6 +39,7 @@ pub mod store;
pub mod sync; pub mod sync;
pub mod sys; pub mod sys;
pub mod terrain; pub mod terrain;
pub mod typed;
pub mod util; pub mod util;
pub mod vol; pub mod vol;
pub mod volumes; pub mod volumes;

View File

@ -453,10 +453,10 @@ pub struct Block {
} }
impl Block { impl Block {
pub fn new(kind: BlockKind, color: Rgb<u8>) -> Self { pub const fn new(kind: BlockKind, color: Rgb<u8>) -> Self {
Self { Self {
kind, kind,
color: color.into_array(), color: [color.r, color.g, color.b],
} }
} }

View File

@ -1,6 +1,7 @@
use super::BlockKind; use super::BlockKind;
use crate::{ use crate::{
assets::{self, Asset}, assets::{self, Asset},
make_case_elim,
vol::{BaseVol, ReadVol, SizedVol, Vox, WriteVol}, vol::{BaseVol, ReadVol, SizedVol, Vox, WriteVol},
volumes::dyna::{Dyna, DynaError}, volumes::dyna::{Dyna, DynaError},
}; };
@ -9,25 +10,29 @@ use serde::Deserialize;
use std::{fs::File, io::BufReader, sync::Arc}; use std::{fs::File, io::BufReader, sync::Arc};
use vek::*; use vek::*;
#[derive(Copy, Clone, PartialEq)] make_case_elim!(
pub enum StructureBlock { structure_block,
None, #[derive(Copy, Clone, PartialEq)]
Grass, #[repr(u32)]
TemperateLeaves, pub enum StructureBlock {
PineLeaves, None = 0,
Acacia, Grass = 1,
Mangrove, TemperateLeaves = 2,
PalmLeavesInner, PineLeaves = 3,
PalmLeavesOuter, Acacia = 4,
Water, Mangrove = 5,
GreenSludge, PalmLeavesInner = 6,
Fruit, PalmLeavesOuter = 7,
Coconut, Water = 8,
Chest, GreenSludge = 9,
Hollow, Fruit = 10,
Liana, Coconut = 11,
Normal(Rgb<u8>), Chest = 12,
} Hollow = 13,
Liana = 14,
Normal(color: Rgb<u8>) = 15,
}
);
impl Vox for StructureBlock { impl Vox for StructureBlock {
fn empty() -> Self { StructureBlock::None } fn empty() -> Self { StructureBlock::None }

View File

@ -22,6 +22,7 @@ impl<Context: SubContext<S>, T, S> Typed<Context, Pure<T>, S> for T {
fn reduce(self, context: Context) -> (Pure<T>, S) { (Pure(self), context.sub_context()) } fn reduce(self, context: Context) -> (Pure<T>, S) { (Pure(self), context.sub_context()) }
} }
#[fundamental]
pub struct ElimCase<Expr, Cases, Type> { pub struct ElimCase<Expr, Cases, Type> {
pub expr: Expr, pub expr: Expr,
pub cases: Cases, pub cases: Cases,
@ -38,7 +39,7 @@ macro_rules! as_item {
#[macro_export] #[macro_export]
macro_rules! make_case_elim { macro_rules! make_case_elim {
($mod:ident, $( #[$ty_attr:meta] )* $vis:vis enum $ty:ident { ($mod:ident, $( #[$ty_attr:meta] )* $vis:vis enum $ty:ident {
$( $constr:ident $( ( $( $arg_name:ident : $arg_ty:ty ),* ) )? = $index:tt ),* $(,)? $( $constr:ident $( ( $( $arg_name:ident : $arg_ty:ty ),* ) )? = $index:expr ),* $(,)?
}) => { }) => {
$crate::as_item! { $crate::as_item! {
$( #[$ty_attr] )* $( #[$ty_attr] )*
@ -48,9 +49,14 @@ macro_rules! make_case_elim {
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(dead_code)]
$vis mod $mod { $vis mod $mod {
use ::serde::{Deserialize, Serialize}; use ::serde::{Deserialize, Serialize};
pub const NUM_VARIANTS: usize = 0 $( + { let _ = $index; 1 } )*;
pub const ALL_INDICES: [u32; NUM_VARIANTS] = [ $( $index, )* ];
pub trait PackedElim { pub trait PackedElim {
$( type $constr; )* $( type $constr; )*
} }
@ -60,25 +66,25 @@ macro_rules! make_case_elim {
$( pub $constr : Elim::$constr, )* $( pub $constr : Elim::$constr, )*
} }
impl<T> PackedElim for $crate::util::Pure<T> { impl<T> PackedElim for $crate::typed::Pure<T> {
$( type $constr = T; )* $( type $constr = T; )*
} }
pub type PureCases<Elim> = Cases<$crate::util::Pure<Elim>>; pub type PureCases<Elim> = Cases<$crate::typed::Pure<Elim>>;
} }
#[allow(unused_parens)] #[allow(unused_parens)]
impl<'a, Elim: $mod::PackedElim, Context, Type, S> impl<'a, Elim: $mod::PackedElim, Context, Type, S>
$crate::util::Typed<Context, Type, S> for $crate::util::ElimCase<&'a $ty, &'a $mod::Cases<Elim>, Type> $crate::typed::Typed<Context, Type, S> for $crate::typed::ElimCase<&'a $ty, &'a $mod::Cases<Elim>, Type>
where where
$( &'a Elim::$constr: $crate::util::Typed<($( ($( &'a $arg_ty, )*), )? Context), Type, S>, )* $( &'a Elim::$constr: $crate::typed::Typed<($( ($( &'a $arg_ty, )*), )? Context), Type, S>, )*
{ {
fn reduce(self, context: Context) -> (Type, S) fn reduce(self, context: Context) -> (Type, S)
{ {
let Self { expr, cases, .. } = self; let Self { expr, cases, .. } = self;
match expr { match expr {
$( $ty::$constr $( ($( $arg_name, )*) )? => $( $ty::$constr $( ($( $arg_name, )*) )? =>
<_ as $crate::util::Typed<_, Type, _>>::reduce( <_ as $crate::typed::Typed<_, Type, _>>::reduce(
&cases.$constr, &cases.$constr,
($( ($( $arg_name, )*), )? context), ($( ($( $arg_name, )*), )? context),
), ),
@ -91,11 +97,11 @@ macro_rules! make_case_elim {
pub fn elim_case<'a, Elim: $mod::PackedElim, Context, S, Type>(&'a self, cases: &'a $mod::Cases<Elim>, context: Context) -> pub fn elim_case<'a, Elim: $mod::PackedElim, Context, S, Type>(&'a self, cases: &'a $mod::Cases<Elim>, context: Context) ->
(Type, S) (Type, S)
where where
$crate::util::ElimCase<&'a $ty, &'a $mod::Cases<Elim>, Type> : $crate::util::Typed<Context, Type, S>, $crate::typed::ElimCase<&'a $ty, &'a $mod::Cases<Elim>, Type> : $crate::typed::Typed<Context, Type, S>,
{ {
use $crate::util::Typed; use $crate::typed::Typed;
let case = $crate::util::ElimCase { let case = $crate::typed::ElimCase {
expr: self, expr: self,
cases, cases,
ty: ::core::marker::PhantomData, ty: ::core::marker::PhantomData,
@ -105,7 +111,7 @@ macro_rules! make_case_elim {
pub fn elim_case_pure<'a, Type>(&'a self, cases: &'a $mod::PureCases<Type>) -> &'a Type pub fn elim_case_pure<'a, Type>(&'a self, cases: &'a $mod::PureCases<Type>) -> &'a Type
{ {
let ($crate::util::Pure(expr), ()) = self.elim_case(cases, ()); let ($crate::typed::Pure(expr), ()) = self.elim_case(cases, ());
expr expr
} }
} }

View File

@ -1,6 +1,5 @@
mod color; mod color;
mod dir; mod dir;
mod typed;
pub const GIT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/githash")); pub const GIT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/githash"));
@ -11,4 +10,3 @@ lazy_static::lazy_static! {
pub use color::*; pub use color::*;
pub use dir::*; pub use dir::*;
pub use typed::*;

View File

@ -1,5 +1,5 @@
#[cfg(not(feature = "worldgen"))] #[cfg(not(feature = "worldgen"))]
use crate::test_world::World; use crate::test_world::{IndexOwned, World};
use common::{generation::ChunkSupplement, terrain::TerrainChunk}; use common::{generation::ChunkSupplement, terrain::TerrainChunk};
use crossbeam::channel; use crossbeam::channel;
use hashbrown::{hash_map::Entry, HashMap}; use hashbrown::{hash_map::Entry, HashMap};
@ -9,11 +9,12 @@ use std::sync::{
Arc, Arc,
}; };
use vek::*; use vek::*;
#[cfg(feature = "worldgen")] use world::World; #[cfg(feature = "worldgen")]
use world::{IndexOwned, World};
type ChunkGenResult = ( type ChunkGenResult = (
Vec2<i32>, Vec2<i32>,
Result<(TerrainChunk, ChunkSupplement), EcsEntity>, Result<(TerrainChunk, ChunkSupplement), Option<EcsEntity>>,
); );
pub struct ChunkGenerator { pub struct ChunkGenerator {
@ -34,10 +35,11 @@ impl ChunkGenerator {
pub fn generate_chunk( pub fn generate_chunk(
&mut self, &mut self,
entity: EcsEntity, entity: Option<EcsEntity>,
key: Vec2<i32>, key: Vec2<i32>,
thread_pool: &mut uvth::ThreadPool, thread_pool: &mut uvth::ThreadPool,
world: Arc<World>, world: Arc<World>,
index: IndexOwned,
) { ) {
let v = if let Entry::Vacant(v) = self.pending_chunks.entry(key) { let v = if let Entry::Vacant(v) = self.pending_chunks.entry(key) {
v v
@ -48,8 +50,9 @@ impl ChunkGenerator {
v.insert(Arc::clone(&cancel)); v.insert(Arc::clone(&cancel));
let chunk_tx = self.chunk_tx.clone(); let chunk_tx = self.chunk_tx.clone();
thread_pool.execute(move || { thread_pool.execute(move || {
let index = index.as_index_ref();
let payload = world let payload = world
.generate_chunk(key, || cancel.load(Ordering::Relaxed)) .generate_chunk(index, key, || cancel.load(Ordering::Relaxed))
.map_err(|_| entity); .map_err(|_| entity);
let _ = chunk_tx.send((key, payload)); let _ = chunk_tx.send((key, payload));
}); });
@ -74,4 +77,10 @@ impl ChunkGenerator {
cancel.store(true, Ordering::Relaxed); cancel.store(true, Ordering::Relaxed);
} }
} }
pub fn cancel_all(&mut self) {
self.pending_chunks.drain().for_each(|(_, cancel)| {
cancel.store(true, Ordering::Relaxed);
});
}
} }

View File

@ -1473,7 +1473,7 @@ fn handle_debug_column(
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?; let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
let chunk = sim.get(chunk_pos)?; let chunk = sim.get(chunk_pos)?;
let col = sampler.get((wpos, server.world.index()))?; let col = sampler.get((wpos, server.index.as_index_ref()))?;
let downhill = chunk.downhill; let downhill = chunk.downhill;
let river = &chunk.river; let river = &chunk.river;
let flux = chunk.flux; let flux = chunk.flux;

View File

@ -55,7 +55,7 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
#[cfg(not(feature = "worldgen"))] #[cfg(not(feature = "worldgen"))]
use test_world::World; use test_world::{IndexOwned, World};
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use uvth::{ThreadPool, ThreadPoolBuilder}; use uvth::{ThreadPool, ThreadPoolBuilder};
use vek::*; use vek::*;
@ -63,7 +63,7 @@ use vek::*;
use world::{ use world::{
civ::SiteKind, civ::SiteKind,
sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP},
World, IndexOwned, World,
}; };
#[macro_use] extern crate diesel; #[macro_use] extern crate diesel;
@ -82,6 +82,7 @@ pub struct Tick(u64);
pub struct Server { pub struct Server {
state: State, state: State,
world: Arc<World>, world: Arc<World>,
index: IndexOwned,
map: WorldMapMsg, map: WorldMapMsg,
network: Network, network: Network,
@ -173,7 +174,7 @@ impl Server {
state.ecs_mut().insert(AliasValidator::new(banned_words)); state.ecs_mut().insert(AliasValidator::new(banned_words));
#[cfg(feature = "worldgen")] #[cfg(feature = "worldgen")]
let world = World::generate(settings.world_seed, WorldOpts { let (world, index) = World::generate(settings.world_seed, WorldOpts {
seed_elements: true, seed_elements: true,
world_file: if let Some(ref opts) = settings.map_file { world_file: if let Some(ref opts) = settings.map_file {
opts.clone() opts.clone()
@ -184,10 +185,10 @@ impl Server {
..WorldOpts::default() ..WorldOpts::default()
}); });
#[cfg(feature = "worldgen")] #[cfg(feature = "worldgen")]
let map = world.get_map_data(); let map = world.get_map_data(index.as_index_ref());
#[cfg(not(feature = "worldgen"))] #[cfg(not(feature = "worldgen"))]
let world = World::generate(settings.world_seed); let (world, index) = World::generate(settings.world_seed);
#[cfg(not(feature = "worldgen"))] #[cfg(not(feature = "worldgen"))]
let map = WorldMapMsg { let map = WorldMapMsg {
dimensions: Vec2::new(1, 1), dimensions: Vec2::new(1, 1),
@ -198,6 +199,7 @@ impl Server {
#[cfg(feature = "worldgen")] #[cfg(feature = "worldgen")]
let spawn_point = { let spawn_point = {
let index = index.as_index_ref();
// NOTE: all of these `.map(|e| e as [type])` calls should compile into no-ops, // NOTE: all of these `.map(|e| e as [type])` calls should compile into no-ops,
// but are needed to be explicit about casting (and to make the compiler stop // but are needed to be explicit about casting (and to make the compiler stop
// complaining) // complaining)
@ -224,11 +226,11 @@ impl Server {
// get a z cache for the collumn in which we want to spawn // get a z cache for the collumn in which we want to spawn
let mut block_sampler = world.sample_blocks(); let mut block_sampler = world.sample_blocks();
let z_cache = block_sampler let z_cache = block_sampler
.get_z_cache(spawn_location, world.index()) .get_z_cache(spawn_location, index)
.expect(&format!("no z_cache found for chunk: {}", spawn_chunk)); .expect(&format!("no z_cache found for chunk: {}", spawn_chunk));
// get the minimum and maximum z values at which there could be soild blocks // get the minimum and maximum z values at which there could be soild blocks
let (min_z, _, max_z) = z_cache.get_z_limits(&mut block_sampler, world.index()); let (min_z, _, max_z) = z_cache.get_z_limits(&mut block_sampler, index);
// round range outwards, so no potential air block is missed // round range outwards, so no potential air block is missed
let min_z = min_z.floor() as i32; let min_z = min_z.floor() as i32;
let max_z = max_z.ceil() as i32; let max_z = max_z.ceil() as i32;
@ -244,7 +246,7 @@ impl Server {
Vec3::new(spawn_location.x, spawn_location.y, *z), Vec3::new(spawn_location.x, spawn_location.y, *z),
Some(&z_cache), Some(&z_cache),
false, false,
world.index(), index,
) )
.map(|b| b.is_air()) .map(|b| b.is_air())
.unwrap_or(false) .unwrap_or(false)
@ -288,6 +290,7 @@ impl Server {
let this = Self { let this = Self {
state, state,
world: Arc::new(world), world: Arc::new(world),
index,
map, map,
network, network,
@ -494,6 +497,42 @@ impl Server {
}, },
}); });
{
// Check for new chunks; cancel and regenerate all chunks if the asset has been
// reloaded. Note that all of these assignments are no-ops, so the
// only work we do here on the fast path is perform a relaxed read on an atomic.
// boolean.
let index = &mut self.index;
let thread_pool = &mut self.thread_pool;
let world = &mut self.world;
let ecs = self.state.ecs_mut();
index.reload_colors_if_changed(|index| {
let mut chunk_generator = ecs.write_resource::<ChunkGenerator>();
let client = ecs.read_storage::<Client>();
let mut terrain = ecs.write_resource::<common::terrain::TerrainGrid>();
// Cancel all pending chunks.
chunk_generator.cancel_all();
if client.is_empty() {
// No cilents, so just clear all terrain.
terrain.clear();
} else {
// There's at leasat one client, so regenerate all chunks.
terrain.iter().for_each(|(pos, _)| {
chunk_generator.generate_chunk(
None,
pos,
thread_pool,
world.clone(),
index.clone(),
);
});
}
});
}
let end_of_server_tick = Instant::now(); let end_of_server_tick = Instant::now();
// 8) Update Metrics // 8) Update Metrics
@ -728,7 +767,13 @@ impl Server {
self.state self.state
.ecs() .ecs()
.write_resource::<ChunkGenerator>() .write_resource::<ChunkGenerator>()
.generate_chunk(entity, key, &mut self.thread_pool, self.world.clone()); .generate_chunk(
Some(entity),
key,
&mut self.thread_pool,
self.world.clone(),
self.index.clone(),
);
} }
fn process_chat_cmd(&mut self, entity: EcsEntity, cmd: String) { fn process_chat_cmd(&mut self, entity: EcsEntity, cmd: String) {

View File

@ -60,7 +60,7 @@ impl<'a> System<'a> for Sys {
'insert_terrain_chunks: while let Some((key, res)) = chunk_generator.recv_new_chunk() { 'insert_terrain_chunks: while let Some((key, res)) = chunk_generator.recv_new_chunk() {
let (chunk, supplement) = match res { let (chunk, supplement) = match res {
Ok((chunk, supplement)) => (chunk, supplement), Ok((chunk, supplement)) => (chunk, supplement),
Err(entity) => { Err(Some(entity)) => {
if let Some(client) = clients.get_mut(entity) { if let Some(client) = clients.get_mut(entity) {
client.notify(ServerMsg::TerrainChunkUpdate { client.notify(ServerMsg::TerrainChunkUpdate {
key, key,
@ -69,6 +69,9 @@ impl<'a> System<'a> for Sys {
} }
continue 'insert_terrain_chunks; continue 'insert_terrain_chunks;
}, },
Err(None) => {
continue 'insert_terrain_chunks;
},
}; };
// Send the chunk to all nearby players. // Send the chunk to all nearby players.
for (view_distance, pos, client) in (&players, &positions, &mut clients) for (view_distance, pos, client) in (&players, &positions, &mut clients)

View File

@ -16,8 +16,25 @@ const DEFAULT_WORLD_CHUNKS_LG: MapSizeLg =
pub struct World; pub struct World;
#[derive(Clone)]
pub struct IndexOwned;
#[derive(Clone, Copy)]
pub struct IndexRef<'a>(&'a IndexOwned);
impl IndexOwned {
pub fn reload_colors_if_changed<R>(
&mut self,
_reload: impl FnOnce(&mut Self) -> R,
) -> Option<R> {
None
}
pub fn as_index_ref(&self) -> IndexRef { IndexRef(self) }
}
impl World { impl World {
pub fn generate(_seed: u32) -> Self { Self } pub fn generate(_seed: u32) -> (Self, IndexOwned) { (Self, IndexOwned) }
pub fn tick(&self, dt: Duration) {} pub fn tick(&self, dt: Duration) {}
@ -26,6 +43,7 @@ impl World {
pub fn generate_chunk( pub fn generate_chunk(
&self, &self,
_index: IndexRef,
chunk_pos: Vec2<i32>, chunk_pos: Vec2<i32>,
_should_continue: impl FnMut() -> bool, _should_continue: impl FnMut() -> bool,
) -> Result<(TerrainChunk, ChunkSupplement), ()> { ) -> Result<(TerrainChunk, ChunkSupplement), ()> {

View File

@ -36,13 +36,14 @@ fn main() {
let mut _map_file = PathBuf::from("./maps"); let mut _map_file = PathBuf::from("./maps");
_map_file.push(map_file); _map_file.push(map_file);
let world = World::generate(5284, WorldOpts { let (world, index) = World::generate(5284, WorldOpts {
seed_elements: false, seed_elements: false,
world_file: sim::FileOpts::LoadAsset(veloren_world::sim::DEFAULT_WORLD_MAP.into()), world_file: sim::FileOpts::LoadAsset(veloren_world::sim::DEFAULT_WORLD_MAP.into()),
// world_file: sim::FileOpts::Load(_map_file), // world_file: sim::FileOpts::Load(_map_file),
// world_file: sim::FileOpts::Save, // world_file: sim::FileOpts::Save,
..WorldOpts::default() ..WorldOpts::default()
}); });
let index = index.as_index_ref();
tracing::info!("Sampling data..."); tracing::info!("Sampling data...");
let sampler = world.sim(); let sampler = world.sim();
let map_size_lg = sampler.map_size_lg(); let map_size_lg = sampler.map_size_lg();
@ -55,7 +56,7 @@ fn main() {
column_sample.get(( column_sample.get((
uniform_idx_as_vec2(map_size_lg, posi) uniform_idx_as_vec2(map_size_lg, posi)
* TerrainChunkSize::RECT_SIZE.map(|e| e as i32), * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
world.index(), index,
)) ))
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -68,7 +69,7 @@ fn main() {
sample_pos( sample_pos(
config, config,
sampler, sampler,
world.index(), index,
samples, samples,
uniform_idx_as_vec2(map_size_lg, posi), uniform_idx_as_vec2(map_size_lg, posi),
) )

View File

@ -3,15 +3,28 @@ mod natural;
use crate::{ use crate::{
column::{ColumnGen, ColumnSample}, column::{ColumnGen, ColumnSample},
util::{RandomField, Sampler, SmallCache}, util::{RandomField, Sampler, SmallCache},
Index, IndexRef,
}; };
use common::{ use common::{
terrain::{structure::StructureBlock, Block, BlockKind, Structure}, terrain::{
structure::{self, StructureBlock},
Block, BlockKind, Structure,
},
vol::{ReadVol, Vox}, vol::{ReadVol, Vox},
}; };
use std::ops::{Div, Mul}; use core::ops::{Div, Mul, Range};
use serde::{Deserialize, Serialize};
use vek::*; use vek::*;
#[derive(Deserialize, Serialize)]
pub struct Colors {
pub pyramid: (u8, u8, u8),
// TODO(@Sharp): After the merge, construct enough infrastructure to make it convenient to
// define mapping functions over the input; i.e. we should be able to interpret some fields as
// defining App<Abs<Fun, Type>, Arg>, where Fun : (Context, Arg) → (S, Type).
pub structure_blocks: structure::structure_block::PureCases<Option<Range<(u8, u8, u8)>>>,
}
pub struct BlockGen<'a> { pub struct BlockGen<'a> {
pub column_cache: SmallCache<Option<ColumnSample<'a>>>, pub column_cache: SmallCache<Option<ColumnSample<'a>>>,
pub column_gen: ColumnGen<'a>, pub column_gen: ColumnGen<'a>,
@ -29,7 +42,7 @@ impl<'a> BlockGen<'a> {
column_gen: &ColumnGen<'a>, column_gen: &ColumnGen<'a>,
cache: &'b mut SmallCache<Option<ColumnSample<'a>>>, cache: &'b mut SmallCache<Option<ColumnSample<'a>>>,
wpos: Vec2<i32>, wpos: Vec2<i32>,
index: &'a Index, index: IndexRef<'a>,
) -> Option<&'b ColumnSample<'a>> { ) -> Option<&'b ColumnSample<'a>> {
cache cache
.get(wpos, |wpos| column_gen.get((wpos, index))) .get(wpos, |wpos| column_gen.get((wpos, index)))
@ -43,7 +56,7 @@ impl<'a> BlockGen<'a> {
close_cliffs: &[(Vec2<i32>, u32); 9], close_cliffs: &[(Vec2<i32>, u32); 9],
cliff_hill: f32, cliff_hill: f32,
tolerance: f32, tolerance: f32,
index: &'a Index, index: IndexRef<'a>,
) -> f32 { ) -> f32 {
close_cliffs.iter().fold( close_cliffs.iter().fold(
0.0f32, 0.0f32,
@ -89,7 +102,7 @@ impl<'a> BlockGen<'a> {
) )
} }
pub fn get_z_cache(&mut self, wpos: Vec2<i32>, index: &'a Index) -> Option<ZCache<'a>> { pub fn get_z_cache(&mut self, wpos: Vec2<i32>, index: IndexRef<'a>) -> Option<ZCache<'a>> {
let BlockGen { let BlockGen {
column_cache, column_cache,
column_gen, column_gen,
@ -143,7 +156,7 @@ impl<'a> BlockGen<'a> {
wpos: Vec3<i32>, wpos: Vec3<i32>,
z_cache: Option<&ZCache>, z_cache: Option<&ZCache>,
only_structures: bool, only_structures: bool,
index: &'a Index, index: IndexRef<'a>,
) -> Option<Block> { ) -> Option<Block> {
let BlockGen { let BlockGen {
column_cache, column_cache,
@ -237,18 +250,7 @@ impl<'a> BlockGen<'a> {
// Sample blocks // Sample blocks
// let stone_col = Rgb::new(195, 187, 201); let water = Block::new(BlockKind::Water, Rgb::zero());
// let dirt_col = Rgb::new(79, 67, 60);
let _air = Block::empty();
// let stone = Block::new(2, stone_col);
// let surface_stone = Block::new(1, Rgb::new(200, 220, 255));
// let dirt = Block::new(1, dirt_col);
// let sand = Block::new(1, Rgb::new(180, 150, 50));
// let warm_stone = Block::new(1, Rgb::new(165, 165, 130));
let water = Block::new(BlockKind::Water, Rgb::new(60, 90, 190));
let grass_depth = (1.5 + 2.0 * chaos).min(height - basement_height); let grass_depth = (1.5 + 2.0 * chaos).min(height - basement_height);
let block = if (wposf.z as f32) < height - grass_depth { let block = if (wposf.z as f32) < height - grass_depth {
@ -389,7 +391,7 @@ impl<'a> BlockGen<'a> {
.iter() .iter()
.find_map(|st| { .find_map(|st| {
let (st, st_sample) = st.as_ref()?; let (st, st_sample) = st.as_ref()?;
st.get(wpos, st_sample) st.get(index, wpos, st_sample)
}) })
.or(block); .or(block);
@ -407,7 +409,7 @@ impl<'a> ZCache<'a> {
pub fn get_z_limits<'b>( pub fn get_z_limits<'b>(
&self, &self,
block_gen: &mut BlockGen<'b>, block_gen: &mut BlockGen<'b>,
index: &'b Index, index: IndexRef<'b>,
) -> (f32, f32, f32) { ) -> (f32, f32, f32) {
let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0); let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0);
let min = min - 4.0; let min = min - 4.0;
@ -496,7 +498,7 @@ impl StructureInfo {
} }
} }
fn get(&self, wpos: Vec3<i32>, sample: &ColumnSample) -> Option<Block> { fn get(&self, index: IndexRef, wpos: Vec3<i32>, sample: &ColumnSample) -> Option<Block> {
match self.meta { match self.meta {
StructureMeta::Pyramid { height } => { StructureMeta::Pyramid { height } => {
if wpos.z - self.pos.z if wpos.z - self.pos.z
@ -505,7 +507,10 @@ impl StructureInfo {
.map(|e: i32| (e.abs() / 2) * 2) .map(|e: i32| (e.abs() / 2) * 2)
.reduce_max() .reduce_max()
{ {
Some(Block::new(BlockKind::Dense, Rgb::new(203, 170, 146))) Some(Block::new(
BlockKind::Dense,
index.colors.block.pyramid.into(),
))
} else { } else {
None None
} }
@ -521,6 +526,7 @@ impl StructureInfo {
.ok() .ok()
.and_then(|b| { .and_then(|b| {
block_from_structure( block_from_structure(
index,
*b, *b,
block_pos, block_pos,
self.pos.into(), self.pos.into(),
@ -534,6 +540,7 @@ impl StructureInfo {
} }
pub fn block_from_structure( pub fn block_from_structure(
index: IndexRef,
sblock: StructureBlock, sblock: StructureBlock,
pos: Vec3<i32>, pos: Vec3<i32>,
structure_pos: Vec2<i32>, structure_pos: Vec2<i32>,
@ -545,119 +552,60 @@ pub fn block_from_structure(
let lerp = ((field.get(Vec3::from(structure_pos)).rem_euclid(256)) as f32 / 255.0) * 0.85 let lerp = ((field.get(Vec3::from(structure_pos)).rem_euclid(256)) as f32 / 255.0) * 0.85
+ ((field.get(pos + std::i32::MAX / 2).rem_euclid(256)) as f32 / 255.0) * 0.15; + ((field.get(pos + std::i32::MAX / 2).rem_euclid(256)) as f32 / 255.0) * 0.15;
let saturate_leaves = |col: Rgb<f32>| {
// /*saturate_srgb(col / 255.0, 0.65)*/
/* let rgb = srgb_to_linear(col / 255.0);
/* let mut xyy = rgb_to_xyy(rgb);
xyy.x *= xyy.x;
xyy.y *= xyy.y;
linear_to_srgb(xyy_to_rgb(xyy).map(|e| e.min(1.0).max(0.0))).map(|e| e * 255.0) */
/* let xyz = rgb_to_xyz(rgb);
let col_adjusted = if xyz.y == 0.0 {
Rgb::zero()
} else {
rgb / xyz.y
};
let col = col_adjusted * col_adjusted * xyz.y;
linear_to_srgb(col).map(|e| e * 255.0) */
/* let mut hsv = rgb_to_hsv(rgb);
hsv.y *= hsv.y;
linear_to_srgb(hsv_to_rgb(hsv).map(|e| e.min(1.0).max(0.0))).map(|e| e * 255.0) */
linear_to_srgb(rgb * rgb).map(|e| e * 255.0) */
col
};
match sblock { match sblock {
StructureBlock::None => None, StructureBlock::None => None,
StructureBlock::Hollow => Some(Block::empty()),
StructureBlock::Grass => Some(Block::new( StructureBlock::Grass => Some(Block::new(
BlockKind::Normal, BlockKind::Normal,
sample.surface_color.map(|e| (e * 255.0) as u8), sample.surface_color.map(|e| (e * 255.0) as u8),
)), )),
StructureBlock::TemperateLeaves => Some(Block::new( StructureBlock::Normal(color) => {
BlockKind::Leaves, Some(Block::new(BlockKind::Normal, color)).filter(|block| !block.is_empty())
Lerp::lerp( },
saturate_leaves(Rgb::new(0.0, 132.0, 94.0)), // Water / sludge throw away their color bits currently, so we don't set anyway.
saturate_leaves(Rgb::new(142.0, 181.0, 0.0)), StructureBlock::Water => Some(Block::new(BlockKind::Water, Rgb::zero())),
lerp,
)
.map(|e| e as u8),
)),
StructureBlock::PineLeaves => Some(Block::new(
BlockKind::Leaves,
Lerp::lerp(Rgb::new(0.0, 60.0, 50.0), Rgb::new(30.0, 100.0, 10.0), lerp)
.map(|e| e as u8),
)),
StructureBlock::PalmLeavesInner => Some(Block::new(
BlockKind::Leaves,
Lerp::lerp(
Rgb::new(61.0, 166.0, 43.0),
Rgb::new(29.0, 130.0, 32.0),
lerp,
)
.map(|e| e as u8),
)),
StructureBlock::PalmLeavesOuter => Some(Block::new(
BlockKind::Leaves,
Lerp::lerp(
Rgb::new(62.0, 171.0, 38.0),
Rgb::new(45.0, 171.0, 65.0),
lerp,
)
.map(|e| e as u8),
)),
StructureBlock::Water => Some(Block::new(
BlockKind::Water,
saturate_leaves(Rgb::new(100.0, 150.0, 255.0)).map(|e| e as u8),
)),
StructureBlock::GreenSludge => Some(Block::new( StructureBlock::GreenSludge => Some(Block::new(
BlockKind::Water, BlockKind::Water,
saturate_leaves(Rgb::new(30.0, 126.0, 23.0)).map(|e| e as u8), // TODO: If/when liquid supports other colors again, revisit this.
)), Rgb::zero(),
StructureBlock::Acacia => Some(Block::new(
BlockKind::Leaves,
Lerp::lerp(
Rgb::new(15.0, 126.0, 50.0),
Rgb::new(30.0, 180.0, 10.0),
lerp,
)
.map(|e| e as u8),
)), )),
// None of these BlockKinds has an orientation, so we just use zero for the other color
// bits.
StructureBlock::Fruit => Some(if field.get(pos + structure_pos) % 3 > 0 { StructureBlock::Fruit => Some(if field.get(pos + structure_pos) % 3 > 0 {
Block::empty() Block::empty()
} else { } else {
Block::new(BlockKind::Apple, Rgb::new(1, 1, 1)) Block::new(BlockKind::Apple, Rgb::zero())
}), }),
StructureBlock::Coconut => Some(if field.get(pos + structure_pos) % 3 > 0 { StructureBlock::Coconut => Some(if field.get(pos + structure_pos) % 3 > 0 {
Block::empty() Block::empty()
} else { } else {
Block::new(BlockKind::Coconut, Rgb::new(1, 1, 1)) Block::new(BlockKind::Coconut, Rgb::zero())
}), }),
StructureBlock::Chest => Some(if structure_seed % 10 < 7 { StructureBlock::Chest => Some(if structure_seed % 10 < 7 {
Block::empty() Block::empty()
} else { } else {
Block::new(BlockKind::Chest, Rgb::new(1, 1, 1)) Block::new(BlockKind::Chest, Rgb::zero())
}), }),
StructureBlock::Liana => Some(Block::new( // We interpolate all these BlockKinds as needed.
BlockKind::Liana, StructureBlock::TemperateLeaves
Lerp::lerp( | StructureBlock::PineLeaves
saturate_leaves(Rgb::new(0.0, 125.0, 107.0)), | StructureBlock::PalmLeavesInner
saturate_leaves(Rgb::new(0.0, 155.0, 129.0)), | StructureBlock::PalmLeavesOuter
lerp, | StructureBlock::Acacia
) | StructureBlock::Liana
.map(|e| e as u8), | StructureBlock::Mangrove => sblock
)), .elim_case_pure(&index.colors.block.structure_blocks)
StructureBlock::Mangrove => Some(Block::new( .as_ref()
BlockKind::Leaves, .map(|range| {
Lerp::lerp( Block::new(
saturate_leaves(Rgb::new(32.0, 56.0, 22.0)), BlockKind::Leaves,
saturate_leaves(Rgb::new(57.0, 69.0, 27.0)), Rgb::<f32>::lerp(
lerp, Rgb::<u8>::from(range.start).map(f32::from),
) Rgb::<u8>::from(range.end).map(f32::from),
.map(|e| e as u8), lerp,
)), )
StructureBlock::Hollow => Some(Block::empty()), .map(|e| e as u8),
StructureBlock::Normal(color) => { )
Some(Block::new(BlockKind::Normal, color)).filter(|block| !block.is_empty()) }),
},
} }
} }

View File

@ -3,7 +3,7 @@ use crate::{
all::ForestKind, all::ForestKind,
column::{ColumnGen, ColumnSample}, column::{ColumnGen, ColumnSample},
util::{RandomPerm, Sampler, SmallCache, UnitChooser}, util::{RandomPerm, Sampler, SmallCache, UnitChooser},
Index, CONFIG, IndexRef, CONFIG,
}; };
use common::terrain::Structure; use common::terrain::Structure;
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -20,7 +20,7 @@ pub fn structure_gen<'a>(
st_pos: Vec2<i32>, st_pos: Vec2<i32>,
st_seed: u32, st_seed: u32,
st_sample: &ColumnSample, st_sample: &ColumnSample,
index: &'a Index, index: IndexRef<'a>,
) -> Option<StructureInfo> { ) -> Option<StructureInfo> {
// Assuming it's a tree... figure out when it SHOULDN'T spawn // Assuming it's a tree... figure out when it SHOULDN'T spawn
let random_seed = (st_seed as f64) / (u32::MAX as f64); let random_seed = (st_seed as f64) / (u32::MAX as f64);

View File

@ -3,7 +3,7 @@ use crate::{
block::StructureMeta, block::StructureMeta,
sim::{local_cells, Cave, Path, RiverKind, SimChunk, WorldSim}, sim::{local_cells, Cave, Path, RiverKind, SimChunk, WorldSim},
util::Sampler, util::Sampler,
Index, CONFIG, IndexRef, CONFIG,
}; };
use common::{ use common::{
terrain::{ terrain::{
@ -13,6 +13,7 @@ use common::{
vol::RectVolSize, vol::RectVolSize,
}; };
use noise::NoiseFn; use noise::NoiseFn;
use serde::{Deserialize, Serialize};
use std::{ use std::{
cmp::Reverse, cmp::Reverse,
f32, f64, f32, f64,
@ -25,6 +26,31 @@ pub struct ColumnGen<'a> {
pub sim: &'a WorldSim, pub sim: &'a WorldSim,
} }
#[derive(Deserialize, Serialize)]
pub struct Colors {
pub cold_grass: (f32, f32, f32),
pub warm_grass: (f32, f32, f32),
pub dark_grass: (f32, f32, f32),
pub wet_grass: (f32, f32, f32),
pub cold_stone: (f32, f32, f32),
pub hot_stone: (f32, f32, f32),
pub warm_stone: (f32, f32, f32),
pub beach_sand: (f32, f32, f32),
pub desert_sand: (f32, f32, f32),
pub snow: (f32, f32, f32),
pub stone_col: (u8, u8, u8),
pub dirt_low: (f32, f32, f32),
pub dirt_high: (f32, f32, f32),
pub snow_high: (f32, f32, f32),
pub warm_stone_high: (f32, f32, f32),
pub grass_high: (f32, f32, f32),
pub tropical_high: (f32, f32, f32),
}
impl<'a> ColumnGen<'a> { impl<'a> ColumnGen<'a> {
pub fn new(sim: &'a WorldSim) -> Self { Self { sim } } pub fn new(sim: &'a WorldSim) -> Self { Self { sim } }
@ -80,7 +106,7 @@ impl<'a> ColumnGen<'a> {
} }
impl<'a> Sampler<'a> for ColumnGen<'a> { impl<'a> Sampler<'a> for ColumnGen<'a> {
type Index = (Vec2<i32>, &'a Index); type Index = (Vec2<i32>, IndexRef<'a>);
type Sample = Option<ColumnSample<'a>>; type Sample = Option<ColumnSample<'a>>;
#[allow(clippy::float_cmp)] // TODO: Pending review in #587 #[allow(clippy::float_cmp)] // TODO: Pending review in #587
@ -765,25 +791,47 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.add(marble_small.sub(0.5).mul(0.25)); .add(marble_small.sub(0.5).mul(0.25));
// Colours // Colours
let cold_grass = Rgb::new(0.0, 0.5, 0.25); let Colors {
let warm_grass = Rgb::new(0.4, 0.8, 0.0); cold_grass,
let dark_grass = Rgb::new(0.15, 0.4, 0.1); warm_grass,
let wet_grass = Rgb::new(0.1, 0.8, 0.2); dark_grass,
let cold_stone = Rgb::new(0.57, 0.67, 0.8); wet_grass,
let hot_stone = Rgb::new(0.07, 0.07, 0.06); cold_stone,
let warm_stone = Rgb::new(0.77, 0.77, 0.64); hot_stone,
let beach_sand = Rgb::new(0.8, 0.75, 0.5); warm_stone,
let desert_sand = Rgb::new(0.7, 0.4, 0.25); beach_sand,
let snow = Rgb::new(0.8, 0.85, 1.0); desert_sand,
snow,
stone_col,
dirt_low,
dirt_high,
snow_high,
warm_stone_high,
grass_high,
tropical_high,
} = index.colors.column;
let stone_col = Rgb::new(195, 187, 201); let cold_grass = cold_grass.into();
let dirt = Lerp::lerp( let warm_grass = warm_grass.into();
Rgb::new(0.075, 0.07, 0.3), let dark_grass = dark_grass.into();
Rgb::new(0.75, 0.55, 0.1), let wet_grass = wet_grass.into();
marble, let cold_stone = cold_stone.into();
); let hot_stone = hot_stone.into();
let tundra = Lerp::lerp(snow, Rgb::new(0.01, 0.3, 0.0), 0.4 + marble * 0.6); let warm_stone: Rgb<f32> = warm_stone.into();
let dead_tundra = Lerp::lerp(warm_stone, Rgb::new(0.3, 0.12, 0.2), marble); let beach_sand = beach_sand.into();
let desert_sand = desert_sand.into();
let snow = snow.into();
let stone_col = stone_col.into();
let dirt_low: Rgb<f32> = dirt_low.into();
let dirt_high = dirt_high.into();
let snow_high = snow_high.into();
let warm_stone_high = warm_stone_high.into();
let grass_high = grass_high.into();
let tropical_high = tropical_high.into();
let dirt = Lerp::lerp(dirt_low, dirt_high, marble);
let tundra = Lerp::lerp(snow, snow_high, 0.4 + marble * 0.6);
let dead_tundra = Lerp::lerp(warm_stone, warm_stone_high, marble);
let cliff = Rgb::lerp(cold_stone, hot_stone, marble); let cliff = Rgb::lerp(cold_stone, hot_stone, marble);
let grass = Rgb::lerp( let grass = Rgb::lerp(
@ -799,14 +847,14 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let tropical = Rgb::lerp( let tropical = Rgb::lerp(
Rgb::lerp( Rgb::lerp(
grass, grass,
Rgb::new(0.15, 0.2, 0.15), grass_high,
marble_small marble_small
.sub(0.5) .sub(0.5)
.mul(0.2) .mul(0.2)
.add(0.75.mul(1.0.sub(humidity))) .add(0.75.mul(1.0.sub(humidity)))
.powf(0.667), .powf(0.667),
), ),
Rgb::new(0.87, 0.62, 0.56), tropical_high,
marble.powf(1.5).sub(0.5).mul(4.0), marble.powf(1.5).sub(0.5).mul(4.0),
); );

View File

@ -1,21 +1,104 @@
use crate::site::Site; use crate::{site::Site, Colors};
use common::store::Store; use common::{
assets::{self, watch::ReloadIndicator},
store::Store,
};
use core::ops::Deref;
use noise::{Seedable, SuperSimplex}; use noise::{Seedable, SuperSimplex};
use std::sync::Arc;
const WORLD_COLORS_MANIFEST: &str = "world.style.colors";
pub struct Index { pub struct Index {
pub seed: u32, pub seed: u32,
pub time: f32, pub time: f32,
pub noise: Noise, pub noise: Noise,
pub sites: Store<Site>, pub sites: Store<Site>,
indicator: ReloadIndicator,
}
/// An owned reference to indexed data.
///
/// The data are split out so that we can replace the colors without disturbing
/// the rest of the index, while also keeping all the adta within a single
/// indirection (though possibly not contiguous).
#[derive(Clone)]
pub struct IndexOwned {
colors: Arc<Colors>,
index: Arc<Index>,
}
impl Deref for IndexOwned {
type Target = Index;
fn deref(&self) -> &Self::Target { &self.index }
}
/// A shared reference to indexed data.
///
/// This is copyable and can be used from either style of index.
#[derive(Clone, Copy)]
pub struct IndexRef<'a> {
pub colors: &'a Colors,
pub index: &'a Index,
}
impl<'a> Deref for IndexRef<'a> {
type Target = Index;
fn deref(&self) -> &Self::Target { &self.index }
} }
impl Index { impl Index {
pub fn new(seed: u32) -> Self { /// NOTE: Panics if the color manifest cannot be loaded.
pub fn new(seed: u32) -> (Self, Arc<Colors>) {
let mut indicator = ReloadIndicator::new();
let colors = assets::load_watched::<Colors>(WORLD_COLORS_MANIFEST, &mut indicator)
.expect("Could not load world colors!");
(
Self {
seed,
time: 0.0,
noise: Noise::new(seed),
sites: Store::default(),
indicator,
},
colors,
)
}
}
impl IndexOwned {
pub fn new(index: Index, colors: Arc<Colors>) -> Self {
Self { Self {
seed, index: Arc::new(index),
time: 0.0, colors,
noise: Noise::new(seed), }
sites: Store::default(), }
/// NOTE: Callback is called only when colors actually have to be reloaded.
/// The server is responsible for making sure that all affected chunks are
/// reloaded; a naive approach will just regenerate every chunk on the
/// server, but it is possible that eventually we can find a better
/// solution.
///
/// Ideally, this should be called about once per tick.
pub fn reload_colors_if_changed<R>(
&mut self,
reload: impl FnOnce(&mut Self) -> R,
) -> Option<R> {
self.indicator.reloaded().then(move || {
// We know the asset was loaded before, so load_expect should be fine.
self.colors = assets::load_expect::<Colors>(WORLD_COLORS_MANIFEST);
reload(self)
})
}
pub fn as_index_ref(&self) -> IndexRef {
IndexRef {
colors: &self.colors,
index: &self.index,
} }
} }
} }

View File

@ -2,7 +2,7 @@ use crate::{
column::ColumnSample, column::ColumnSample,
sim::SimChunk, sim::SimChunk,
util::{RandomField, Sampler}, util::{RandomField, Sampler},
Index, IndexRef,
}; };
use common::{ use common::{
assets, comp, assets, comp,
@ -13,12 +13,19 @@ use common::{
}; };
use noise::NoiseFn; use noise::NoiseFn;
use rand::prelude::*; use rand::prelude::*;
use serde::{Deserialize, Serialize};
use std::{ use std::{
f32, f32,
ops::{Mul, Sub}, ops::{Mul, Sub},
}; };
use vek::*; use vek::*;
#[derive(Deserialize, Serialize)]
pub struct Colors {
pub bridge: (u8, u8, u8),
pub stalagtite: (u8, u8, u8),
}
fn close(x: f32, tgt: f32, falloff: f32) -> f32 { fn close(x: f32, tgt: f32, falloff: f32) -> f32 {
(1.0 - (x - tgt).abs() / falloff).max(0.0).powf(0.5) (1.0 - (x - tgt).abs() / falloff).max(0.0).powf(0.5)
} }
@ -27,7 +34,7 @@ pub fn apply_scatter_to<'a>(
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol), vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
index: &Index, index: IndexRef,
chunk: &SimChunk, chunk: &SimChunk,
) { ) {
use BlockKind::*; use BlockKind::*;
@ -150,7 +157,7 @@ pub fn apply_paths_to<'a>(
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol), vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
_index: &Index, index: IndexRef,
) { ) {
for y in 0..vol.size_xy().y as i32 { for y in 0..vol.size_xy().y as i32 {
for x in 0..vol.size_xy().x as i32 { for x in 0..vol.size_xy().x as i32 {
@ -213,7 +220,10 @@ pub fn apply_paths_to<'a>(
let _ = vol.set( let _ = vol.set(
Vec3::new(offs.x, offs.y, surface_z + z), Vec3::new(offs.x, offs.y, surface_z + z),
if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 { if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 {
Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8)) Block::new(
BlockKind::Normal,
noisy_color(index.colors.layer.bridge.into(), 8),
)
} else { } else {
let path_color = path.surface_color( let path_color = path.surface_color(
col_sample.sub_surface_color.map(|e| (e * 255.0) as u8), col_sample.sub_surface_color.map(|e| (e * 255.0) as u8),
@ -238,7 +248,7 @@ pub fn apply_caves_to<'a>(
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol), vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
index: &Index, index: IndexRef,
) { ) {
for y in 0..vol.size_xy().y as i32 { for y in 0..vol.size_xy().y as i32 {
for x in 0..vol.size_xy().x as i32 { for x in 0..vol.size_xy().x as i32 {
@ -297,7 +307,7 @@ pub fn apply_caves_to<'a>(
for z in cave_roof - stalagtites..cave_roof { for z in cave_roof - stalagtites..cave_roof {
let _ = vol.set( let _ = vol.set(
Vec3::new(offs.x, offs.y, z), Vec3::new(offs.x, offs.y, z),
Block::new(BlockKind::Rock, Rgb::broadcast(200)), Block::new(BlockKind::Rock, index.colors.layer.stalagtite.into()),
); );
} }
@ -325,7 +335,7 @@ pub fn apply_caves_supplement<'a>(
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &(impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol), vol: &(impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
index: &Index, index: IndexRef,
supplement: &mut ChunkSupplement, supplement: &mut ChunkSupplement,
) { ) {
for y in 0..vol.size_xy().y as i32 { for y in 0..vol.size_xy().y as i32 {

View File

@ -3,6 +3,7 @@
#![allow(clippy::option_map_unit_fn)] #![allow(clippy::option_map_unit_fn)]
#![feature( #![feature(
arbitrary_enum_discriminant, arbitrary_enum_discriminant,
bool_to_option,
const_generics, const_generics,
const_panic, const_panic,
label_break_value, label_break_value,
@ -25,6 +26,7 @@ pub mod util;
pub use crate::config::CONFIG; pub use crate::config::CONFIG;
pub use block::BlockGen; pub use block::BlockGen;
pub use column::ColumnSample; pub use column::ColumnSample;
pub use index::{IndexOwned, IndexRef};
use crate::{ use crate::{
column::ColumnGen, column::ColumnGen,
@ -32,6 +34,7 @@ use crate::{
util::{Grid, Sampler}, util::{Grid, Sampler},
}; };
use common::{ use common::{
assets::{self, Asset},
comp::{self, bird_medium, critter, quadruped_low, quadruped_medium, quadruped_small}, comp::{self, bird_medium, critter, quadruped_low, quadruped_medium, quadruped_small},
generation::{ChunkSupplement, EntityInfo}, generation::{ChunkSupplement, EntityInfo},
msg::server::WorldMapMsg, msg::server::WorldMapMsg,
@ -39,7 +42,8 @@ use common::{
vol::{ReadVol, RectVolSize, Vox, WriteVol}, vol::{ReadVol, RectVolSize, Vox, WriteVol},
}; };
use rand::Rng; use rand::Rng;
use std::time::Duration; use serde::{Deserialize, Serialize};
use std::{fs::File, io::BufReader, time::Duration};
use vek::*; use vek::*;
#[derive(Debug)] #[derive(Debug)]
@ -50,35 +54,51 @@ pub enum Error {
pub struct World { pub struct World {
sim: sim::WorldSim, sim: sim::WorldSim,
civs: civ::Civs, civs: civ::Civs,
index: Index, }
#[derive(Deserialize, Serialize)]
pub struct Colors {
pub deep_stone_color: (u8, u8, u8),
pub block: block::Colors,
pub column: column::Colors,
pub layer: layer::Colors,
pub site: site::Colors,
}
impl Asset for Colors {
const ENDINGS: &'static [&'static str] = &["ron"];
fn parse(buf_reader: BufReader<File>) -> Result<Self, assets::Error> {
ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error)
}
} }
impl World { impl World {
pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self { pub fn generate(seed: u32, opts: sim::WorldOpts) -> (Self, IndexOwned) {
// NOTE: Generating index first in order to quickly fail if the color manifest
// is broken.
let (mut index, colors) = Index::new(seed);
let mut sim = sim::WorldSim::generate(seed, opts); let mut sim = sim::WorldSim::generate(seed, opts);
let mut index = Index::new(seed);
let civs = civ::Civs::generate(seed, &mut sim, &mut index); let civs = civ::Civs::generate(seed, &mut sim, &mut index);
sim2::simulate(&mut index, &mut sim); sim2::simulate(&mut index, &mut sim);
Self { sim, civs, index } (Self { sim, civs }, IndexOwned::new(index, colors))
} }
pub fn sim(&self) -> &sim::WorldSim { &self.sim } pub fn sim(&self) -> &sim::WorldSim { &self.sim }
pub fn civs(&self) -> &civ::Civs { &self.civs } pub fn civs(&self) -> &civ::Civs { &self.civs }
pub fn index(&self) -> &Index { &self.index }
pub fn tick(&self, _dt: Duration) { pub fn tick(&self, _dt: Duration) {
// TODO // TODO
} }
pub fn get_map_data(&self) -> WorldMapMsg { self.sim.get_map(&self.index) } pub fn get_map_data(&self, index: IndexRef) -> WorldMapMsg { self.sim.get_map(index) }
pub fn sample_columns( pub fn sample_columns(
&self, &self,
) -> impl Sampler<Index = (Vec2<i32>, &Index), Sample = Option<ColumnSample>> + '_ { ) -> impl Sampler<Index = (Vec2<i32>, IndexRef), Sample = Option<ColumnSample>> + '_ {
ColumnGen::new(&self.sim) ColumnGen::new(&self.sim)
} }
@ -87,6 +107,7 @@ impl World {
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 #[allow(clippy::or_fun_call)] // TODO: Pending review in #587
pub fn generate_chunk( pub fn generate_chunk(
&self, &self,
index: IndexRef,
chunk_pos: Vec2<i32>, chunk_pos: Vec2<i32>,
// TODO: misleading name // TODO: misleading name
mut should_continue: impl FnMut() -> bool, mut should_continue: impl FnMut() -> bool,
@ -97,7 +118,7 @@ impl World {
let grid_border = 4; let grid_border = 4;
let zcache_grid = Grid::populate_from( let zcache_grid = Grid::populate_from(
TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2, TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2,
|offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs, &self.index), |offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs, index),
); );
let air = Block::empty(); let air = Block::empty();
@ -107,9 +128,9 @@ impl World {
.get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2) .get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2)
.and_then(|zcache| zcache.as_ref()) .and_then(|zcache| zcache.as_ref())
.map(|zcache| zcache.sample.stone_col) .map(|zcache| zcache.sample.stone_col)
.unwrap_or(Rgb::new(125, 120, 130)), .unwrap_or(index.colors.deep_stone_color.into()),
); );
let water = Block::new(BlockKind::Water, Rgb::new(60, 90, 190)); let water = Block::new(BlockKind::Water, Rgb::zero());
let _chunk_size2d = TerrainChunkSize::RECT_SIZE; let _chunk_size2d = TerrainChunkSize::RECT_SIZE;
let (base_z, sim_chunk) = match self let (base_z, sim_chunk) = match self
@ -153,7 +174,7 @@ impl World {
}; };
let (min_z, only_structures_min_z, max_z) = let (min_z, only_structures_min_z, max_z) =
z_cache.get_z_limits(&mut sampler, &self.index); z_cache.get_z_limits(&mut sampler, index);
(base_z..min_z as i32).for_each(|z| { (base_z..min_z as i32).for_each(|z| {
let _ = chunk.set(Vec3::new(x, y, z), stone); let _ = chunk.set(Vec3::new(x, y, z), stone);
@ -165,7 +186,7 @@ impl World {
let only_structures = lpos.z >= only_structures_min_z as i32; let only_structures = lpos.z >= only_structures_min_z as i32;
if let Some(block) = if let Some(block) =
sampler.get_with_z_cache(wpos, Some(&z_cache), only_structures, &self.index) sampler.get_with_z_cache(wpos, Some(&z_cache), only_structures, index)
{ {
let _ = chunk.set(lpos, block); let _ = chunk.set(lpos, block);
} }
@ -184,13 +205,13 @@ impl World {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
// Apply layers (paths, caves, etc.) // Apply layers (paths, caves, etc.)
layer::apply_scatter_to(chunk_wpos2d, sample_get, &mut chunk, &self.index, sim_chunk); layer::apply_scatter_to(chunk_wpos2d, sample_get, &mut chunk, index, sim_chunk);
layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk, &self.index); layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk, index);
layer::apply_caves_to(chunk_wpos2d, sample_get, &mut chunk, &self.index); layer::apply_caves_to(chunk_wpos2d, sample_get, &mut chunk, index);
// Apply site generation // Apply site generation
sim_chunk.sites.iter().for_each(|site| { sim_chunk.sites.iter().for_each(|site| {
self.index.sites[*site].apply_to(chunk_wpos2d, sample_get, &mut chunk) index.sites[*site].apply_to(index, chunk_wpos2d, sample_get, &mut chunk)
}); });
let gen_entity_pos = || { let gen_entity_pos = || {
@ -243,18 +264,13 @@ impl World {
chunk_wpos2d, chunk_wpos2d,
sample_get, sample_get,
&chunk, &chunk,
&self.index, index,
&mut supplement, &mut supplement,
); );
// Apply site supplementary information // Apply site supplementary information
sim_chunk.sites.iter().for_each(|site| { sim_chunk.sites.iter().for_each(|site| {
self.index.sites[*site].apply_supplement( index.sites[*site].apply_supplement(&mut rng, chunk_wpos2d, sample_get, &mut supplement)
&mut rng,
chunk_wpos2d,
sample_get,
&mut supplement,
)
}); });
Ok((chunk, supplement)) Ok((chunk, supplement))

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
column::ColumnSample, column::ColumnSample,
sim::{RiverKind, WorldSim}, sim::{RiverKind, WorldSim},
Index, CONFIG, IndexRef, CONFIG,
}; };
use common::{ use common::{
terrain::{ terrain::{
@ -63,10 +63,15 @@ pub fn sample_wpos(config: &MapConfig, sampler: &WorldSim, wpos: Vec2<i32>) -> f
/// are to be used for some reason, one should pass a custom function to /// are to be used for some reason, one should pass a custom function to
/// generate instead (e.g. one that just looks up the color in a cached /// generate instead (e.g. one that just looks up the color in a cached
/// array). /// array).
// NOTE: Deliberately not putting Rgb colors here in the config file; they
// aren't hot reloaded anyway, and for various reasons they're probably not a
// good idea to update in that way (for example, we currently want water colors
// to match voxygen's). Eventually we'll fix these sorts of issues in some
// other way.
pub fn sample_pos( pub fn sample_pos(
config: &MapConfig, config: &MapConfig,
sampler: &WorldSim, sampler: &WorldSim,
index: &Index, index: IndexRef,
samples: Option<&[Option<ColumnSample>]>, samples: Option<&[Option<ColumnSample>]>,
pos: Vec2<i32>, pos: Vec2<i32>,
) -> MapSample { ) -> MapSample {

View File

@ -29,7 +29,7 @@ use crate::{
column::ColumnGen, column::ColumnGen,
site::Site, site::Site,
util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d, LOCALITY, NEIGHBORS}, util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d, LOCALITY, NEIGHBORS},
Index, CONFIG, IndexRef, CONFIG,
}; };
use common::{ use common::{
assets, assets,
@ -1408,7 +1408,7 @@ impl WorldSim {
/// Draw a map of the world based on chunk information. Returns a buffer of /// Draw a map of the world based on chunk information. Returns a buffer of
/// u32s. /// u32s.
pub fn get_map(&self, index: &Index) -> WorldMapMsg { pub fn get_map(&self, index: IndexRef) -> WorldMapMsg {
let mut map_config = MapConfig::orthographic( let mut map_config = MapConfig::orthographic(
self.map_size_lg(), self.map_size_lg(),
core::ops::RangeInclusive::new(CONFIG.sea_level, CONFIG.sea_level + self.max_height), core::ops::RangeInclusive::new(CONFIG.sea_level, CONFIG.sea_level + self.max_height),

View File

@ -3,9 +3,10 @@ use crate::{
column::ColumnSample, column::ColumnSample,
sim::WorldSim, sim::WorldSim,
site::settlement::building::{ site::settlement::building::{
archetype::keep::{Attr, Keep as KeepArchetype}, archetype::keep::{Attr, FlagColor, Keep as KeepArchetype, StoneColor},
Archetype, Ori, Archetype, Ori,
}, },
IndexRef,
}; };
use common::{ use common::{
generation::ChunkSupplement, generation::ChunkSupplement,
@ -14,6 +15,7 @@ use common::{
}; };
use core::f32; use core::f32;
use rand::prelude::*; use rand::prelude::*;
use serde::{Deserialize, Serialize};
use vek::*; use vek::*;
struct Keep { struct Keep {
@ -47,6 +49,9 @@ pub struct GenCtx<'a, R: Rng> {
rng: &'a mut R, rng: &'a mut R,
} }
#[derive(Deserialize, Serialize)]
pub struct Colors;
impl Castle { impl Castle {
#[allow(clippy::let_and_return)] // TODO: Pending review in #587 #[allow(clippy::let_and_return)] // TODO: Pending review in #587
pub fn generate(wpos: Vec2<i32>, sim: Option<&mut WorldSim>, rng: &mut impl Rng) -> Self { pub fn generate(wpos: Vec2<i32>, sim: Option<&mut WorldSim>, rng: &mut impl Rng) -> Self {
@ -167,6 +172,7 @@ impl Castle {
pub fn apply_to<'a>( pub fn apply_to<'a>(
&'a self, &'a self,
index: IndexRef,
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol), vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
@ -273,14 +279,14 @@ impl Castle {
let keep_archetype = KeepArchetype { let keep_archetype = KeepArchetype {
flag_color: if self.evil { flag_color: if self.evil {
Rgb::new(80, 10, 130) FlagColor::Evil
} else { } else {
Rgb::new(200, 80, 40) FlagColor::Good
}, },
stone_color: if self.evil { stone_color: if self.evil {
Rgb::new(65, 60, 55) StoneColor::Evil
} else { } else {
Rgb::new(100, 100, 110) StoneColor::Good
}, },
}; };
@ -294,6 +300,7 @@ impl Castle {
} }
let mut mask = keep_archetype.draw( let mut mask = keep_archetype.draw(
index,
Vec3::from(wall_rpos) + Vec3::unit_z() * wpos.z - wall_alt, Vec3::from(wall_rpos) + Vec3::unit_z() * wpos.z - wall_alt,
wall_dist, wall_dist,
border_pos, border_pos,
@ -323,6 +330,7 @@ impl Castle {
let border_pos = (tower_wpos - wpos).xy().map(|e| e.abs()); let border_pos = (tower_wpos - wpos).xy().map(|e| e.abs());
mask = mask.resolve_with(keep_archetype.draw( mask = mask.resolve_with(keep_archetype.draw(
index,
if (tower_wpos.x - wpos.x).abs() < (tower_wpos.y - wpos.y).abs() { if (tower_wpos.x - wpos.x).abs() < (tower_wpos.y - wpos.y).abs() {
wpos - tower_wpos wpos - tower_wpos
} else { } else {
@ -364,6 +372,7 @@ impl Castle {
let border_pos = (keep_wpos - wpos).xy().map(|e| e.abs()); let border_pos = (keep_wpos - wpos).xy().map(|e| e.abs());
mask = mask.resolve_with(keep_archetype.draw( mask = mask.resolve_with(keep_archetype.draw(
index,
if (keep_wpos.x - wpos.x).abs() < (keep_wpos.y - wpos.y).abs() { if (keep_wpos.x - wpos.x).abs() < (keep_wpos.y - wpos.y).abs() {
wpos - keep_wpos wpos - keep_wpos
} else { } else {

View File

@ -5,6 +5,7 @@ use crate::{
sim::WorldSim, sim::WorldSim,
site::BlockMask, site::BlockMask,
util::{attempt, Grid, RandomField, Sampler, CARDINALS, DIRS}, util::{attempt, Grid, RandomField, Sampler, CARDINALS, DIRS},
IndexRef,
}; };
use common::{ use common::{
assets, assets,
@ -20,6 +21,7 @@ use core::{f32, hash::BuildHasherDefault};
use fxhash::FxHasher64; use fxhash::FxHasher64;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use rand::prelude::*; use rand::prelude::*;
use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
use vek::*; use vek::*;
@ -37,6 +39,11 @@ pub struct GenCtx<'a, R: Rng> {
rng: &'a mut R, rng: &'a mut R,
} }
#[derive(Deserialize, Serialize)]
pub struct Colors {
pub stone: (u8, u8, u8),
}
const ALT_OFFSET: i32 = -2; const ALT_OFFSET: i32 = -2;
const LEVELS: usize = 5; const LEVELS: usize = 5;
@ -80,6 +87,7 @@ impl Dungeon {
pub fn apply_to<'a>( pub fn apply_to<'a>(
&'a self, &'a self,
index: IndexRef,
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol), vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
@ -112,7 +120,14 @@ impl Dungeon {
.ok() .ok()
.copied() .copied()
.map(|sb| { .map(|sb| {
block_from_structure(sb, spos, self.origin, self.seed, col_sample) block_from_structure(
index,
sb,
spos,
self.origin,
self.seed,
col_sample,
)
}) })
.unwrap_or(None) .unwrap_or(None)
{ {
@ -125,7 +140,7 @@ impl Dungeon {
for floor in &self.floors { for floor in &self.floors {
z -= floor.total_depth(); z -= floor.total_depth();
let mut sampler = floor.col_sampler(rpos, z); let mut sampler = floor.col_sampler(index, rpos, z);
for rz in 0..floor.total_depth() { for rz in 0..floor.total_depth() {
if let Some(block) = sampler(rz).finish() { if let Some(block) = sampler(rz).finish() {
@ -574,16 +589,29 @@ impl Floor {
} }
#[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587 #[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587
pub fn col_sampler(&self, pos: Vec2<i32>, floor_z: i32) -> impl FnMut(i32) -> BlockMask + '_ { pub fn col_sampler<'a>(
&'a self,
index: IndexRef<'a>,
pos: Vec2<i32>,
floor_z: i32,
) -> impl FnMut(i32) -> BlockMask + 'a {
let rpos = pos - self.tile_offset * TILE_SIZE; let rpos = pos - self.tile_offset * TILE_SIZE;
let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE));
let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2;
let rtile_pos = rpos - tile_center; let rtile_pos = rpos - tile_center;
let colors = &index.colors.site.dungeon;
let empty = BlockMask::new(Block::empty(), 1); let empty = BlockMask::new(Block::empty(), 1);
let make_staircase = move |pos: Vec3<i32>, radius: f32, inner_radius: f32, stretch: f32| { let make_staircase = move |pos: Vec3<i32>, radius: f32, inner_radius: f32, stretch: f32| {
let stone = BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(150, 150, 175)), 5); let stone = BlockMask::new(
Block::new(
BlockKind::Normal,
/* Rgb::new(150, 150, 175) */ colors.stone.into(),
),
5,
);
if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) { if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) {
stone stone

View File

@ -10,15 +10,23 @@ pub use self::{
settlement::Settlement, settlement::Settlement,
}; };
use crate::column::ColumnSample; use crate::{column::ColumnSample, IndexRef};
use common::{ use common::{
generation::ChunkSupplement, generation::ChunkSupplement,
terrain::Block, terrain::Block,
vol::{BaseVol, ReadVol, RectSizedVol, WriteVol}, vol::{BaseVol, ReadVol, RectSizedVol, WriteVol},
}; };
use rand::Rng; use rand::Rng;
use serde::{Deserialize, Serialize};
use vek::*; use vek::*;
#[derive(Deserialize, Serialize)]
pub struct Colors {
pub castle: castle::Colors,
pub dungeon: dungeon::Colors,
pub settlement: settlement::Colors,
}
pub struct SpawnRules { pub struct SpawnRules {
pub trees: bool, pub trees: bool,
} }
@ -86,14 +94,15 @@ impl Site {
pub fn apply_to<'a>( pub fn apply_to<'a>(
&'a self, &'a self,
index: IndexRef,
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol), vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
) { ) {
match &self.kind { match &self.kind {
SiteKind::Settlement(s) => s.apply_to(wpos2d, get_column, vol), SiteKind::Settlement(s) => s.apply_to(index, wpos2d, get_column, vol),
SiteKind::Dungeon(d) => d.apply_to(wpos2d, get_column, vol), SiteKind::Dungeon(d) => d.apply_to(index, wpos2d, get_column, vol),
SiteKind::Castle(c) => c.apply_to(wpos2d, get_column, vol), SiteKind::Castle(c) => c.apply_to(index, wpos2d, get_column, vol),
} }
} }

View File

@ -4,64 +4,103 @@ use super::{super::skeleton::*, Archetype};
use crate::{ use crate::{
site::BlockMask, site::BlockMask,
util::{RandomField, Sampler}, util::{RandomField, Sampler},
IndexRef,
}; };
use common::{ use common::{
make_case_elim,
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
vol::Vox, vol::Vox,
}; };
use rand::prelude::*; use rand::prelude::*;
use serde::{Deserialize, Serialize};
use vek::*; use vek::*;
pub struct ColorTheme { #[derive(Deserialize, Serialize)]
roof: Rgb<u8>, pub struct Colors {
wall: Rgb<u8>, pub foundation: (u8, u8, u8),
support: Rgb<u8>, pub floor: (u8, u8, u8),
pub roof: roof_color::PureCases<(u8, u8, u8)>,
pub wall: wall_color::PureCases<(u8, u8, u8)>,
pub support: support_color::PureCases<(u8, u8, u8)>,
} }
const ROOF_COLORS: &[Rgb<u8>] = &[ pub struct ColorTheme {
// Rgb::new(0x1D, 0x4D, 0x45), roof: RoofColor,
// Rgb::new(0xB3, 0x7D, 0x60), wall: WallColor,
// Rgb::new(0xAC, 0x5D, 0x26), support: SupportColor,
// Rgb::new(0x32, 0x46, 0x6B), }
// Rgb::new(0x2B, 0x19, 0x0F),
// Rgb::new(0x93, 0x78, 0x51), make_case_elim!(
// Rgb::new(0x92, 0x57, 0x24), roof_color,
// Rgb::new(0x4A, 0x4E, 0x4E), #[repr(u32)]
// Rgb::new(0x2F, 0x32, 0x47), #[derive(Clone, Copy)]
// Rgb::new(0x8F, 0x35, 0x43), pub enum RoofColor {
// Rgb::new(0x6D, 0x1E, 0x3A), Roof1 = 0,
// Rgb::new(0x6D, 0xA7, 0x80), Roof2 = 1,
// Rgb::new(0x4F, 0xA0, 0x95), Roof3 = 2,
// Rgb::new(0xE2, 0xB9, 0x99), Roof4 = 3,
// Rgb::new(0x7A, 0x30, 0x22), Roof5 = 4,
// Rgb::new(0x4A, 0x06, 0x08), Roof6 = 5,
// Rgb::new(0x8E, 0xB4, 0x57), Roof7 = 6,
Rgb::new(0x99, 0x5E, 0x54), }
Rgb::new(0x43, 0x63, 0x64), );
Rgb::new(0x76, 0x6D, 0x68),
Rgb::new(0x7B, 0x41, 0x61), make_case_elim!(
Rgb::new(0x52, 0x20, 0x20), wall_color,
Rgb::new(0x1A, 0x4A, 0x59), #[repr(u32)]
Rgb::new(0xCC, 0x76, 0x4E), #[derive(Clone, Copy)]
pub enum WallColor {
Wall1 = 0,
Wall2 = 1,
Wall3 = 2,
Wall4 = 3,
Wall5 = 4,
Wall6 = 5,
Wall7 = 6,
Wall8 = 7,
Wall9 = 8,
}
);
make_case_elim!(
support_color,
#[repr(u32)]
#[derive(Clone, Copy)]
pub enum SupportColor {
Support1 = 0,
Support2 = 1,
Support3 = 2,
Support4 = 3,
}
);
const ROOF_COLORS: [RoofColor; roof_color::NUM_VARIANTS] = [
RoofColor::Roof1,
RoofColor::Roof2,
RoofColor::Roof3,
RoofColor::Roof4,
RoofColor::Roof4,
RoofColor::Roof6,
RoofColor::Roof7,
]; ];
const WALL_COLORS: &[Rgb<u8>] = &[ const WALL_COLORS: [WallColor; wall_color::NUM_VARIANTS] = [
Rgb::new(200, 180, 150), WallColor::Wall1,
Rgb::new(0xB8, 0xB4, 0xA4), WallColor::Wall2,
Rgb::new(0x76, 0x6D, 0x68), WallColor::Wall3,
Rgb::new(0xF3, 0xC9, 0x8F), WallColor::Wall4,
Rgb::new(0xD3, 0xB7, 0x99), WallColor::Wall5,
Rgb::new(0xE1, 0xAB, 0x91), WallColor::Wall6,
Rgb::new(0x82, 0x57, 0x4C), WallColor::Wall7,
Rgb::new(0xB9, 0x96, 0x77), WallColor::Wall8,
Rgb::new(0xAE, 0x8D, 0x9C), WallColor::Wall9,
]; ];
const SUPPORT_COLORS: &[Rgb<u8>] = &[ const SUPPORT_COLORS: [SupportColor; support_color::NUM_VARIANTS] = [
Rgb::new(60, 45, 30), SupportColor::Support1,
Rgb::new(0x65, 0x55, 0x56), SupportColor::Support2,
Rgb::new(0x53, 0x33, 0x13), SupportColor::Support3,
Rgb::new(0x58, 0x42, 0x33), SupportColor::Support4,
]; ];
pub struct House { pub struct House {
@ -210,6 +249,7 @@ impl Archetype for House {
#[allow(clippy::int_plus_one)] // TODO: Pending review in #587 #[allow(clippy::int_plus_one)] // TODO: Pending review in #587
fn draw( fn draw(
&self, &self,
index: IndexRef,
_pos: Vec3<i32>, _pos: Vec3<i32>,
dist: i32, dist: i32,
bound_offset: Vec2<i32>, bound_offset: Vec2<i32>,
@ -220,6 +260,11 @@ impl Archetype for House {
_len: i32, _len: i32,
attr: &Self::Attr, attr: &Self::Attr,
) -> BlockMask { ) -> BlockMask {
let colors = &index.colors.site.settlement.building.archetype.house;
let roof_color = *self.colors.roof.elim_case_pure(&colors.roof);
let wall_color = *self.colors.wall.elim_case_pure(&colors.wall);
let support_color = *self.colors.support.elim_case_pure(&colors.support);
let profile = Vec2::new(bound_offset.x, z); let profile = Vec2::new(bound_offset.x, z);
let make_meta = |ori| { let make_meta = |ori| {
@ -240,6 +285,7 @@ impl Archetype for House {
BlockMask::new( BlockMask::new(
Block::new( Block::new(
BlockKind::Normal, BlockKind::Normal,
// TODO: Clarify exactly how this affects the color.
Rgb::new(r, g, b) Rgb::new(r, g, b)
.map(|e: u8| e.saturating_add((nz & 0x0F) as u8).saturating_sub(8)), .map(|e: u8| e.saturating_add((nz & 0x0F) as u8).saturating_sub(8)),
), ),
@ -253,11 +299,11 @@ impl Archetype for House {
let foundation_layer = internal_layer + 1; let foundation_layer = internal_layer + 1;
let floor_layer = foundation_layer + 1; let floor_layer = foundation_layer + 1;
let foundation = make_block((100, 100, 100)).with_priority(foundation_layer); let foundation = make_block(colors.foundation).with_priority(foundation_layer);
let log = make_block(self.colors.support.into_tuple()); let log = make_block(support_color);
let floor = make_block((100, 75, 50)); let floor = make_block(colors.floor);
let wall = make_block(self.colors.wall.into_tuple()).with_priority(facade_layer); let wall = make_block(wall_color).with_priority(facade_layer);
let roof = make_block(self.colors.roof.into_tuple()).with_priority(facade_layer - 1); let roof = make_block(roof_color).with_priority(facade_layer - 1);
let empty = BlockMask::nothing(); let empty = BlockMask::nothing();
let internal = BlockMask::new(Block::empty(), internal_layer); let internal = BlockMask::new(Block::empty(), internal_layer);
let end_window = BlockMask::new( let end_window = BlockMask::new(

View File

@ -2,17 +2,29 @@ use super::{super::skeleton::*, Archetype};
use crate::{ use crate::{
site::BlockMask, site::BlockMask,
util::{RandomField, Sampler}, util::{RandomField, Sampler},
IndexRef,
}; };
use common::{ use common::{
make_case_elim,
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
vol::Vox, vol::Vox,
}; };
use rand::prelude::*; use rand::prelude::*;
use serde::{Deserialize, Serialize};
use vek::*; use vek::*;
#[derive(Deserialize, Serialize)]
pub struct Colors {
pub brick_base: (u8, u8, u8),
pub floor_base: (u8, u8, u8),
pub pole: (u8, u8, u8),
pub flag: flag_color::PureCases<(u8, u8, u8)>,
pub stone: stone_color::PureCases<(u8, u8, u8)>,
}
pub struct Keep { pub struct Keep {
pub flag_color: Rgb<u8>, pub flag_color: FlagColor,
pub stone_color: Rgb<u8>, pub stone_color: StoneColor,
} }
pub struct Attr { pub struct Attr {
@ -24,6 +36,24 @@ pub struct Attr {
pub has_doors: bool, pub has_doors: bool,
} }
make_case_elim!(
flag_color,
#[repr(u32)]
pub enum FlagColor {
Good = 0,
Evil = 1,
}
);
make_case_elim!(
stone_color,
#[repr(u32)]
pub enum StoneColor {
Good = 0,
Evil = 1,
}
);
impl Archetype for Keep { impl Archetype for Keep {
type Attr = Attr; type Attr = Attr;
@ -71,8 +101,8 @@ impl Archetype for Keep {
( (
Self { Self {
flag_color: Rgb::new(200, 80, 40), flag_color: FlagColor::Good,
stone_color: Rgb::new(100, 100, 110), stone_color: StoneColor::Good,
}, },
skel, skel,
) )
@ -81,6 +111,7 @@ impl Archetype for Keep {
#[allow(clippy::if_same_then_else)] // TODO: Pending review in #587 #[allow(clippy::if_same_then_else)] // TODO: Pending review in #587
fn draw( fn draw(
&self, &self,
index: IndexRef,
pos: Vec3<i32>, pos: Vec3<i32>,
_dist: i32, _dist: i32,
bound_offset: Vec2<i32>, bound_offset: Vec2<i32>,
@ -91,6 +122,11 @@ impl Archetype for Keep {
_len: i32, _len: i32,
attr: &Self::Attr, attr: &Self::Attr,
) -> BlockMask { ) -> BlockMask {
let dungeon_stone = index.colors.site.dungeon.stone;
let colors = &index.colors.site.settlement.building.archetype.keep;
let flag_color = self.flag_color.elim_case_pure(&colors.flag);
let stone_color = self.stone_color.elim_case_pure(&colors.stone);
let profile = Vec2::new(bound_offset.x, z); let profile = Vec2::new(bound_offset.x, z);
let weak_layer = 1; let weak_layer = 1;
@ -118,30 +154,35 @@ impl Archetype for Keep {
let brick_tex_pos = (pos + Vec3::new(pos.z, pos.z, 0)) / Vec3::new(2, 2, 1); let brick_tex_pos = (pos + Vec3::new(pos.z, pos.z, 0)) / Vec3::new(2, 2, 1);
let brick_tex = RandomField::new(0).get(brick_tex_pos) as u8 % 24; let brick_tex = RandomField::new(0).get(brick_tex_pos) as u8 % 24;
let foundation = make_block(80 + brick_tex, 80 + brick_tex, 80 + brick_tex); let foundation = make_block(
colors.brick_base.0 + brick_tex,
colors.brick_base.1 + brick_tex,
colors.brick_base.2 + brick_tex,
);
let wall = make_block( let wall = make_block(
self.stone_color.r + brick_tex, stone_color.0 + brick_tex,
self.stone_color.g + brick_tex, stone_color.1 + brick_tex,
self.stone_color.b + brick_tex, stone_color.2 + brick_tex,
); );
let window = BlockMask::new( let window = BlockMask::new(
Block::new(BlockKind::Window1, make_meta(ori.flip())), Block::new(BlockKind::Window1, make_meta(ori.flip())),
normal_layer, normal_layer,
); );
let floor = make_block( let floor = make_block(
80 + (pos.y.abs() % 2) as u8 * 15, colors.floor_base.0 + (pos.y.abs() % 2) as u8 * 15,
60 + (pos.y.abs() % 2) as u8 * 15, colors.floor_base.1 + (pos.y.abs() % 2) as u8 * 15,
10 + (pos.y.abs() % 2) as u8 * 15, colors.floor_base.2 + (pos.y.abs() % 2) as u8 * 15,
) )
.with_priority(important_layer); .with_priority(important_layer);
let pole = make_block(90, 70, 50).with_priority(important_layer); let pole =
let flag = make_block(self.flag_color.r, self.flag_color.g, self.flag_color.b) make_block(colors.pole.0, colors.pole.1, colors.pole.2).with_priority(important_layer);
.with_priority(important_layer); let flag =
make_block(flag_color.0, flag_color.1, flag_color.2).with_priority(important_layer);
let internal = BlockMask::new(Block::empty(), internal_layer); let internal = BlockMask::new(Block::empty(), internal_layer);
let empty = BlockMask::nothing(); let empty = BlockMask::nothing();
let make_staircase = move |pos: Vec3<i32>, radius: f32, inner_radius: f32, stretch: f32| { let make_staircase = move |pos: Vec3<i32>, radius: f32, inner_radius: f32, stretch: f32| {
let stone = BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(150, 150, 175)), 5); let stone = BlockMask::new(Block::new(BlockKind::Normal, dungeon_stone.into()), 5);
if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) { if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) {
stone stone

View File

@ -2,10 +2,17 @@ pub mod house;
pub mod keep; pub mod keep;
use super::skeleton::*; use super::skeleton::*;
use crate::site::BlockMask; use crate::{site::BlockMask, IndexRef};
use rand::prelude::*; use rand::prelude::*;
use serde::{Deserialize, Serialize};
use vek::*; use vek::*;
#[derive(Deserialize, Serialize)]
pub struct Colors {
pub house: house::Colors,
pub keep: keep::Colors,
}
pub trait Archetype { pub trait Archetype {
type Attr; type Attr;
@ -16,6 +23,7 @@ pub trait Archetype {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn draw( fn draw(
&self, &self,
index: IndexRef,
pos: Vec3<i32>, pos: Vec3<i32>,
dist: i32, dist: i32,
bound_offset: Vec2<i32>, bound_offset: Vec2<i32>,

View File

@ -7,10 +7,17 @@ pub use self::{
skeleton::*, skeleton::*,
}; };
use crate::IndexRef;
use common::terrain::Block; use common::terrain::Block;
use rand::prelude::*; use rand::prelude::*;
use serde::{Deserialize, Serialize};
use vek::*; use vek::*;
#[derive(Deserialize, Serialize)]
pub struct Colors {
pub archetype: archetype::Colors,
}
pub struct Building<A: Archetype> { pub struct Building<A: Archetype> {
skel: Skeleton<A::Attr>, skel: Skeleton<A::Attr>,
archetype: A, archetype: A,
@ -46,13 +53,14 @@ impl<A: Archetype> Building<A> {
} }
} }
pub fn sample(&self, pos: Vec3<i32>) -> Option<Block> { pub fn sample(&self, index: IndexRef, pos: Vec3<i32>) -> Option<Block> {
let rpos = pos - self.origin; let rpos = pos - self.origin;
self.skel self.skel
.sample_closest( .sample_closest(
rpos, rpos,
|pos, dist, bound_offset, center_offset, ori, branch| { |pos, dist, bound_offset, center_offset, ori, branch| {
self.archetype.draw( self.archetype.draw(
index,
pos, pos,
dist, dist,
bound_offset, bound_offset,

View File

@ -10,6 +10,7 @@ use crate::{
column::ColumnSample, column::ColumnSample,
sim::WorldSim, sim::WorldSim,
util::{RandomField, Sampler, StructureGen2d}, util::{RandomField, Sampler, StructureGen2d},
IndexRef,
}; };
use common::{ use common::{
assets, assets,
@ -25,9 +26,30 @@ use common::{
use fxhash::FxHasher64; use fxhash::FxHasher64;
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use rand::prelude::*; use rand::prelude::*;
use serde::{Deserialize, Serialize};
use std::{collections::VecDeque, f32, hash::BuildHasherDefault}; use std::{collections::VecDeque, f32, hash::BuildHasherDefault};
use vek::*; use vek::*;
#[derive(Deserialize, Serialize)]
pub struct Colors {
pub building: building::Colors,
pub plot_town_path: (u8, u8, u8),
pub plot_field_dirt: (u8, u8, u8),
pub plot_field_mound: (u8, u8, u8),
pub wall_low: (u8, u8, u8),
pub wall_high: (u8, u8, u8),
pub tower_color: (u8, u8, u8),
pub plot_dirt: (u8, u8, u8),
pub plot_grass: (u8, u8, u8),
pub plot_water: (u8, u8, u8),
pub plot_town: (u8, u8, u8),
}
#[allow(dead_code)] #[allow(dead_code)]
pub fn gradient(line: [Vec2<f32>; 2]) -> f32 { pub fn gradient(line: [Vec2<f32>; 2]) -> f32 {
let r = (line[0].y - line[1].y) / (line[0].x - line[1].x); let r = (line[0].y - line[1].y) / (line[0].x - line[1].x);
@ -109,10 +131,10 @@ impl Structure {
} }
} }
pub fn sample(&self, rpos: Vec3<i32>) -> Option<Block> { pub fn sample(&self, index: IndexRef, rpos: Vec3<i32>) -> Option<Block> {
match &self.kind { match &self.kind {
StructureKind::House(house) => house.sample(rpos), StructureKind::House(house) => house.sample(index, rpos),
StructureKind::Keep(keep) => keep.sample(rpos), StructureKind::Keep(keep) => keep.sample(index, rpos),
} }
} }
} }
@ -527,10 +549,13 @@ impl Settlement {
#[allow(clippy::modulo_one)] // TODO: Pending review in #587 #[allow(clippy::modulo_one)] // TODO: Pending review in #587
pub fn apply_to<'a>( pub fn apply_to<'a>(
&'a self, &'a self,
index: IndexRef,
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol), vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
) { ) {
let colors = &index.colors.site.settlement;
for y in 0..vol.size_xy().y as i32 { for y in 0..vol.size_xy().y as i32 {
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);
@ -586,47 +611,6 @@ impl Settlement {
} }
} }
// 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, 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())
// * (col.riverless_alt + 5.0 - col.alt).max(0.0)
// * 1.75
// + 3.0) as i32,
// )
// } else {
// (0.0, 3)
// };
// let surface_z = (col.riverless_alt + bridge_offset).floor() as i32;
// for z in inset - depth..inset {
// 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)) },
// );
// }
// let head_space = (8 - (dist * 0.25).powf(6.0).round() as i32).max(1);
// for z in inset..inset + head_space {
// let pos = Vec3::new(offs.x, offs.y, surface_z + z);
// if vol.get(pos).unwrap().kind() != BlockKind::Water {
// let _ = vol.set(pos, Block::empty());
// }
// }
// // Ground colour
// } else
{ {
let mut surface_block = None; let mut surface_block = None;
@ -634,9 +618,9 @@ impl Settlement {
|seed, n| self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n; |seed, n| self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n;
let color = match sample.plot { let color = match sample.plot {
Some(Plot::Dirt) => Some(Rgb::new(90, 70, 50)), Some(Plot::Dirt) => Some(colors.plot_dirt.into()),
Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)), Some(Plot::Grass) => Some(colors.plot_grass.into()),
Some(Plot::Water) => Some(Rgb::new(100, 150, 250)), Some(Plot::Water) => Some(colors.plot_water.into()),
//Some(Plot::Town { district }) => None, //Some(Plot::Town { district }) => None,
Some(Plot::Town { .. }) => { Some(Plot::Town { .. }) => {
if let Some((_, path_nearest, _, _)) = col_sample.path { if let Some((_, path_nearest, _, _)) = col_sample.path {
@ -659,13 +643,16 @@ impl Settlement {
} }
} }
Some(Rgb::new(100, 95, 65).map2(Rgb::iota(), |e: u8, i: i32| { Some(Rgb::from(colors.plot_town_path).map2(
e.saturating_add( Rgb::iota(),
(self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 1) |e: u8, i: i32| {
as u8, e.saturating_add(
) (self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 1)
.saturating_sub(8) as u8,
})) )
.saturating_sub(8)
},
))
}, },
Some(Plot::Field { seed, crop, .. }) => { Some(Plot::Field { seed, crop, .. }) => {
let furrow_dirs = [ let furrow_dirs = [
@ -677,12 +664,13 @@ 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 in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2; let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2;
let dirt = Rgb::new(80, 55, 35).map(|e| { let dirt = Rgb::<u8>::from(colors.plot_field_dirt).map(|e| {
e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32)) % 32) e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32)) % 32)
as u8 as u8
}); });
let mound = let mound = Rgb::<u8>::from(colors.plot_field_mound)
Rgb::new(70, 80, 30).map(|e| e + roll(0, 8) as u8).map(|e| { .map(|e| e + roll(0, 8) as u8)
.map(|e| {
e + (self.noise.get(Vec3::broadcast((seed % 4096 + 1) as i32)) e + (self.noise.get(Vec3::broadcast((seed % 4096 + 1) as i32))
% 32) as u8 % 32) as u8
}); });
@ -769,8 +757,8 @@ impl Settlement {
// Walls // Walls
if let Some((WayKind::Wall, dist, _)) = sample.way { if let Some((WayKind::Wall, dist, _)) = sample.way {
let color = Lerp::lerp( let color = Lerp::lerp(
Rgb::new(130i32, 100, 0), Rgb::<u8>::from(colors.wall_low).map(i32::from),
Rgb::new(90, 70, 50), Rgb::<u8>::from(colors.wall_high).map(i32::from),
(RandomField::new(0).get(wpos2d.into()) % 256) as f32 / 256.0, (RandomField::new(0).get(wpos2d.into()) % 256) as f32 / 256.0,
) )
.map(|e| (e % 256) as u8); .map(|e| (e % 256) as u8);
@ -797,7 +785,7 @@ impl Settlement {
for z in -2..16 { for z in -2..16 {
let _ = vol.set( let _ = vol.set(
Vec3::new(offs.x, offs.y, surface_z + z), Vec3::new(offs.x, offs.y, surface_z + z),
Block::new(BlockKind::Normal, Rgb::new(50, 50, 50)), Block::new(BlockKind::Normal, colors.tower_color.into()),
); );
} }
} }
@ -832,7 +820,7 @@ impl Settlement {
let wpos = Vec3::from(self.origin) + rpos; let wpos = Vec3::from(self.origin) + rpos;
let coffs = wpos - Vec3::from(wpos2d); let coffs = wpos - Vec3::from(wpos2d);
if let Some(block) = structure.sample(rpos) { if let Some(block) = structure.sample(index, rpos) {
let _ = vol.set(coffs, block); let _ = vol.set(coffs, block);
} }
} }
@ -938,30 +926,24 @@ impl Settlement {
} }
} }
pub fn get_color(&self, pos: Vec2<i32>) -> Option<Rgb<u8>> { pub fn get_color(&self, index: IndexRef, pos: Vec2<i32>) -> Option<Rgb<u8>> {
let colors = &index.colors.site.settlement;
let sample = self.land.get_at_block(pos); 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 { match sample.plot {
Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)), Some(Plot::Dirt) => return Some(colors.plot_dirt.into()),
Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)), Some(Plot::Grass) => return Some(colors.plot_grass.into()),
Some(Plot::Water) => return Some(Rgb::new(100, 150, 250)), Some(Plot::Water) => return Some(colors.plot_water.into()),
Some(Plot::Town { .. }) => { Some(Plot::Town { .. }) => {
return Some(Rgb::new(150, 110, 60).map2(Rgb::iota(), |e: u8, i: i32| { return Some(
e.saturating_add((self.noise.get(Vec3::new(pos.x, pos.y, i * 5)) % 16) as u8) Rgb::from(colors.plot_town).map2(Rgb::iota(), |e: u8, i: i32| {
e.saturating_add(
(self.noise.get(Vec3::new(pos.x, pos.y, i * 5)) % 16) as u8,
)
.saturating_sub(8) .saturating_sub(8)
})); }),
);
}, },
Some(Plot::Field { seed, .. }) => { Some(Plot::Field { seed, .. }) => {
let furrow_dirs = [ let furrow_dirs = [
@ -972,6 +954,12 @@ 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;
// NOTE: Very hard to understand how to make this dynamically configurable. The
// base values can easily cause the others to go out of range, and there's some
// weird scaling going on. For now, we just let these remain hardcoded.
//
// FIXME: Rewrite this so that validity is not so heavily dependent on the exact
// color values.
return Some(Rgb::new( return Some(Rgb::new(
if furrow { if furrow {
100 100
@ -1003,6 +991,8 @@ pub enum Crop {
Sunflower, Sunflower,
} }
// NOTE: No support for struct variants in make_case_elim yet, unfortunately, so
// we can't use it.
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq)]
pub enum Plot { pub enum Plot {
Hazard, Hazard,

View File

@ -1,4 +1,3 @@
// pub mod boruvka;
pub mod fast_noise; pub mod fast_noise;
pub mod grid; pub mod grid;
pub mod map_vec; pub mod map_vec;