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
// 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
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(
arbitrary_enum_discriminant,
const_checked_int_methods,
fundamental,
option_unwrap_none,
bool_to_option,
label_break_value,
@ -38,6 +39,7 @@ pub mod store;
pub mod sync;
pub mod sys;
pub mod terrain;
pub mod typed;
pub mod util;
pub mod vol;
pub mod volumes;

View File

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

View File

@ -1,6 +1,7 @@
use super::BlockKind;
use crate::{
assets::{self, Asset},
make_case_elim,
vol::{BaseVol, ReadVol, SizedVol, Vox, WriteVol},
volumes::dyna::{Dyna, DynaError},
};
@ -9,25 +10,29 @@ use serde::Deserialize;
use std::{fs::File, io::BufReader, sync::Arc};
use vek::*;
#[derive(Copy, Clone, PartialEq)]
pub enum StructureBlock {
None,
Grass,
TemperateLeaves,
PineLeaves,
Acacia,
Mangrove,
PalmLeavesInner,
PalmLeavesOuter,
Water,
GreenSludge,
Fruit,
Coconut,
Chest,
Hollow,
Liana,
Normal(Rgb<u8>),
}
make_case_elim!(
structure_block,
#[derive(Copy, Clone, PartialEq)]
#[repr(u32)]
pub enum StructureBlock {
None = 0,
Grass = 1,
TemperateLeaves = 2,
PineLeaves = 3,
Acacia = 4,
Mangrove = 5,
PalmLeavesInner = 6,
PalmLeavesOuter = 7,
Water = 8,
GreenSludge = 9,
Fruit = 10,
Coconut = 11,
Chest = 12,
Hollow = 13,
Liana = 14,
Normal(color: Rgb<u8>) = 15,
}
);
impl Vox for StructureBlock {
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()) }
}
#[fundamental]
pub struct ElimCase<Expr, Cases, Type> {
pub expr: Expr,
pub cases: Cases,
@ -38,7 +39,7 @@ macro_rules! as_item {
#[macro_export]
macro_rules! make_case_elim {
($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! {
$( #[$ty_attr] )*
@ -48,9 +49,14 @@ macro_rules! make_case_elim {
}
#[allow(non_snake_case)]
#[allow(dead_code)]
$vis mod $mod {
use ::serde::{Deserialize, Serialize};
pub const NUM_VARIANTS: usize = 0 $( + { let _ = $index; 1 } )*;
pub const ALL_INDICES: [u32; NUM_VARIANTS] = [ $( $index, )* ];
pub trait PackedElim {
$( type $constr; )*
}
@ -60,25 +66,25 @@ macro_rules! make_case_elim {
$( pub $constr : Elim::$constr, )*
}
impl<T> PackedElim for $crate::util::Pure<T> {
impl<T> PackedElim for $crate::typed::Pure<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)]
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
$( &'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)
{
let Self { expr, cases, .. } = self;
match expr {
$( $ty::$constr $( ($( $arg_name, )*) )? =>
<_ as $crate::util::Typed<_, Type, _>>::reduce(
<_ as $crate::typed::Typed<_, Type, _>>::reduce(
&cases.$constr,
($( ($( $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) ->
(Type, S)
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,
cases,
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
{
let ($crate::util::Pure(expr), ()) = self.elim_case(cases, ());
let ($crate::typed::Pure(expr), ()) = self.elim_case(cases, ());
expr
}
}

View File

@ -1,6 +1,5 @@
mod color;
mod dir;
mod typed;
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 dir::*;
pub use typed::*;

View File

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

View File

@ -55,7 +55,7 @@ use std::{
time::{Duration, Instant},
};
#[cfg(not(feature = "worldgen"))]
use test_world::World;
use test_world::{IndexOwned, World};
use tracing::{debug, error, info, warn};
use uvth::{ThreadPool, ThreadPoolBuilder};
use vek::*;
@ -63,7 +63,7 @@ use vek::*;
use world::{
civ::SiteKind,
sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP},
World,
IndexOwned, World,
};
#[macro_use] extern crate diesel;
@ -82,6 +82,7 @@ pub struct Tick(u64);
pub struct Server {
state: State,
world: Arc<World>,
index: IndexOwned,
map: WorldMapMsg,
network: Network,
@ -173,7 +174,7 @@ impl Server {
state.ecs_mut().insert(AliasValidator::new(banned_words));
#[cfg(feature = "worldgen")]
let world = World::generate(settings.world_seed, WorldOpts {
let (world, index) = World::generate(settings.world_seed, WorldOpts {
seed_elements: true,
world_file: if let Some(ref opts) = settings.map_file {
opts.clone()
@ -184,10 +185,10 @@ impl Server {
..WorldOpts::default()
});
#[cfg(feature = "worldgen")]
let map = world.get_map_data();
let map = world.get_map_data(index.as_index_ref());
#[cfg(not(feature = "worldgen"))]
let world = World::generate(settings.world_seed);
let (world, index) = World::generate(settings.world_seed);
#[cfg(not(feature = "worldgen"))]
let map = WorldMapMsg {
dimensions: Vec2::new(1, 1),
@ -198,6 +199,7 @@ impl Server {
#[cfg(feature = "worldgen")]
let spawn_point = {
let index = index.as_index_ref();
// 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
// complaining)
@ -224,11 +226,11 @@ impl Server {
// get a z cache for the collumn in which we want to spawn
let mut block_sampler = world.sample_blocks();
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));
// 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
let min_z = min_z.floor() 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),
Some(&z_cache),
false,
world.index(),
index,
)
.map(|b| b.is_air())
.unwrap_or(false)
@ -288,6 +290,7 @@ impl Server {
let this = Self {
state,
world: Arc::new(world),
index,
map,
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();
// 8) Update Metrics
@ -728,7 +767,13 @@ impl Server {
self.state
.ecs()
.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) {

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() {
let (chunk, supplement) = match res {
Ok((chunk, supplement)) => (chunk, supplement),
Err(entity) => {
Err(Some(entity)) => {
if let Some(client) = clients.get_mut(entity) {
client.notify(ServerMsg::TerrainChunkUpdate {
key,
@ -69,6 +69,9 @@ impl<'a> System<'a> for Sys {
}
continue 'insert_terrain_chunks;
},
Err(None) => {
continue 'insert_terrain_chunks;
},
};
// Send the chunk to all nearby players.
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;
#[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 {
pub fn generate(_seed: u32) -> Self { Self }
pub fn generate(_seed: u32) -> (Self, IndexOwned) { (Self, IndexOwned) }
pub fn tick(&self, dt: Duration) {}
@ -26,6 +43,7 @@ impl World {
pub fn generate_chunk(
&self,
_index: IndexRef,
chunk_pos: Vec2<i32>,
_should_continue: impl FnMut() -> bool,
) -> Result<(TerrainChunk, ChunkSupplement), ()> {

View File

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

View File

@ -3,15 +3,28 @@ mod natural;
use crate::{
column::{ColumnGen, ColumnSample},
util::{RandomField, Sampler, SmallCache},
Index,
IndexRef,
};
use common::{
terrain::{structure::StructureBlock, Block, BlockKind, Structure},
terrain::{
structure::{self, StructureBlock},
Block, BlockKind, Structure,
},
vol::{ReadVol, Vox},
};
use std::ops::{Div, Mul};
use core::ops::{Div, Mul, Range};
use serde::{Deserialize, Serialize};
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 column_cache: SmallCache<Option<ColumnSample<'a>>>,
pub column_gen: ColumnGen<'a>,
@ -29,7 +42,7 @@ impl<'a> BlockGen<'a> {
column_gen: &ColumnGen<'a>,
cache: &'b mut SmallCache<Option<ColumnSample<'a>>>,
wpos: Vec2<i32>,
index: &'a Index,
index: IndexRef<'a>,
) -> Option<&'b ColumnSample<'a>> {
cache
.get(wpos, |wpos| column_gen.get((wpos, index)))
@ -43,7 +56,7 @@ impl<'a> BlockGen<'a> {
close_cliffs: &[(Vec2<i32>, u32); 9],
cliff_hill: f32,
tolerance: f32,
index: &'a Index,
index: IndexRef<'a>,
) -> f32 {
close_cliffs.iter().fold(
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 {
column_cache,
column_gen,
@ -143,7 +156,7 @@ impl<'a> BlockGen<'a> {
wpos: Vec3<i32>,
z_cache: Option<&ZCache>,
only_structures: bool,
index: &'a Index,
index: IndexRef<'a>,
) -> Option<Block> {
let BlockGen {
column_cache,
@ -237,18 +250,7 @@ impl<'a> BlockGen<'a> {
// Sample blocks
// let stone_col = Rgb::new(195, 187, 201);
// 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 water = Block::new(BlockKind::Water, Rgb::zero());
let grass_depth = (1.5 + 2.0 * chaos).min(height - basement_height);
let block = if (wposf.z as f32) < height - grass_depth {
@ -389,7 +391,7 @@ impl<'a> BlockGen<'a> {
.iter()
.find_map(|st| {
let (st, st_sample) = st.as_ref()?;
st.get(wpos, st_sample)
st.get(index, wpos, st_sample)
})
.or(block);
@ -407,7 +409,7 @@ impl<'a> ZCache<'a> {
pub fn get_z_limits<'b>(
&self,
block_gen: &mut BlockGen<'b>,
index: &'b Index,
index: IndexRef<'b>,
) -> (f32, f32, f32) {
let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.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 {
StructureMeta::Pyramid { height } => {
if wpos.z - self.pos.z
@ -505,7 +507,10 @@ impl StructureInfo {
.map(|e: i32| (e.abs() / 2) * 2)
.reduce_max()
{
Some(Block::new(BlockKind::Dense, Rgb::new(203, 170, 146)))
Some(Block::new(
BlockKind::Dense,
index.colors.block.pyramid.into(),
))
} else {
None
}
@ -521,6 +526,7 @@ impl StructureInfo {
.ok()
.and_then(|b| {
block_from_structure(
index,
*b,
block_pos,
self.pos.into(),
@ -534,6 +540,7 @@ impl StructureInfo {
}
pub fn block_from_structure(
index: IndexRef,
sblock: StructureBlock,
pos: Vec3<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
+ ((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 {
StructureBlock::None => None,
StructureBlock::Hollow => Some(Block::empty()),
StructureBlock::Grass => Some(Block::new(
BlockKind::Normal,
sample.surface_color.map(|e| (e * 255.0) as u8),
)),
StructureBlock::TemperateLeaves => Some(Block::new(
BlockKind::Leaves,
Lerp::lerp(
saturate_leaves(Rgb::new(0.0, 132.0, 94.0)),
saturate_leaves(Rgb::new(142.0, 181.0, 0.0)),
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::Normal(color) => {
Some(Block::new(BlockKind::Normal, color)).filter(|block| !block.is_empty())
},
// Water / sludge throw away their color bits currently, so we don't set anyway.
StructureBlock::Water => Some(Block::new(BlockKind::Water, Rgb::zero())),
StructureBlock::GreenSludge => Some(Block::new(
BlockKind::Water,
saturate_leaves(Rgb::new(30.0, 126.0, 23.0)).map(|e| e as u8),
)),
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),
// TODO: If/when liquid supports other colors again, revisit this.
Rgb::zero(),
)),
// 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 {
Block::empty()
} 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 {
Block::empty()
} else {
Block::new(BlockKind::Coconut, Rgb::new(1, 1, 1))
Block::new(BlockKind::Coconut, Rgb::zero())
}),
StructureBlock::Chest => Some(if structure_seed % 10 < 7 {
Block::empty()
} else {
Block::new(BlockKind::Chest, Rgb::new(1, 1, 1))
Block::new(BlockKind::Chest, Rgb::zero())
}),
StructureBlock::Liana => Some(Block::new(
BlockKind::Liana,
Lerp::lerp(
saturate_leaves(Rgb::new(0.0, 125.0, 107.0)),
saturate_leaves(Rgb::new(0.0, 155.0, 129.0)),
lerp,
)
.map(|e| e as u8),
)),
StructureBlock::Mangrove => Some(Block::new(
// We interpolate all these BlockKinds as needed.
StructureBlock::TemperateLeaves
| StructureBlock::PineLeaves
| StructureBlock::PalmLeavesInner
| StructureBlock::PalmLeavesOuter
| StructureBlock::Acacia
| StructureBlock::Liana
| StructureBlock::Mangrove => sblock
.elim_case_pure(&index.colors.block.structure_blocks)
.as_ref()
.map(|range| {
Block::new(
BlockKind::Leaves,
Lerp::lerp(
saturate_leaves(Rgb::new(32.0, 56.0, 22.0)),
saturate_leaves(Rgb::new(57.0, 69.0, 27.0)),
Rgb::<f32>::lerp(
Rgb::<u8>::from(range.start).map(f32::from),
Rgb::<u8>::from(range.end).map(f32::from),
lerp,
)
.map(|e| e as u8),
)),
StructureBlock::Hollow => Some(Block::empty()),
StructureBlock::Normal(color) => {
Some(Block::new(BlockKind::Normal, color)).filter(|block| !block.is_empty())
},
)
}),
}
}

View File

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

View File

@ -3,7 +3,7 @@ use crate::{
block::StructureMeta,
sim::{local_cells, Cave, Path, RiverKind, SimChunk, WorldSim},
util::Sampler,
Index, CONFIG,
IndexRef, CONFIG,
};
use common::{
terrain::{
@ -13,6 +13,7 @@ use common::{
vol::RectVolSize,
};
use noise::NoiseFn;
use serde::{Deserialize, Serialize};
use std::{
cmp::Reverse,
f32, f64,
@ -25,6 +26,31 @@ pub struct ColumnGen<'a> {
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> {
pub fn new(sim: &'a WorldSim) -> Self { Self { sim } }
@ -80,7 +106,7 @@ impl<'a> 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>>;
#[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));
// Colours
let cold_grass = Rgb::new(0.0, 0.5, 0.25);
let warm_grass = Rgb::new(0.4, 0.8, 0.0);
let dark_grass = Rgb::new(0.15, 0.4, 0.1);
let wet_grass = Rgb::new(0.1, 0.8, 0.2);
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.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 Colors {
cold_grass,
warm_grass,
dark_grass,
wet_grass,
cold_stone,
hot_stone,
warm_stone,
beach_sand,
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 dirt = Lerp::lerp(
Rgb::new(0.075, 0.07, 0.3),
Rgb::new(0.75, 0.55, 0.1),
marble,
);
let tundra = Lerp::lerp(snow, Rgb::new(0.01, 0.3, 0.0), 0.4 + marble * 0.6);
let dead_tundra = Lerp::lerp(warm_stone, Rgb::new(0.3, 0.12, 0.2), marble);
let cold_grass = cold_grass.into();
let warm_grass = warm_grass.into();
let dark_grass = dark_grass.into();
let wet_grass = wet_grass.into();
let cold_stone = cold_stone.into();
let hot_stone = hot_stone.into();
let warm_stone: Rgb<f32> = warm_stone.into();
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 grass = Rgb::lerp(
@ -799,14 +847,14 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let tropical = Rgb::lerp(
Rgb::lerp(
grass,
Rgb::new(0.15, 0.2, 0.15),
grass_high,
marble_small
.sub(0.5)
.mul(0.2)
.add(0.75.mul(1.0.sub(humidity)))
.powf(0.667),
),
Rgb::new(0.87, 0.62, 0.56),
tropical_high,
marble.powf(1.5).sub(0.5).mul(4.0),
);

View File

@ -1,21 +1,104 @@
use crate::site::Site;
use common::store::Store;
use crate::{site::Site, Colors};
use common::{
assets::{self, watch::ReloadIndicator},
store::Store,
};
use core::ops::Deref;
use noise::{Seedable, SuperSimplex};
use std::sync::Arc;
const WORLD_COLORS_MANIFEST: &str = "world.style.colors";
pub struct Index {
pub seed: u32,
pub time: f32,
pub noise: Noise,
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 {
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 {
index: Arc::new(index),
colors,
}
}
/// 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,
sim::SimChunk,
util::{RandomField, Sampler},
Index,
IndexRef,
};
use common::{
assets, comp,
@ -13,12 +13,19 @@ use common::{
};
use noise::NoiseFn;
use rand::prelude::*;
use serde::{Deserialize, Serialize};
use std::{
f32,
ops::{Mul, Sub},
};
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 {
(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>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
index: &Index,
index: IndexRef,
chunk: &SimChunk,
) {
use BlockKind::*;
@ -150,7 +157,7 @@ pub fn apply_paths_to<'a>(
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
_index: &Index,
index: IndexRef,
) {
for y in 0..vol.size_xy().y 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(
Vec3::new(offs.x, offs.y, surface_z + z),
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 {
let path_color = path.surface_color(
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>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
index: &Index,
index: IndexRef,
) {
for y in 0..vol.size_xy().y 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 {
let _ = vol.set(
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>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &(impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
index: &Index,
index: IndexRef,
supplement: &mut ChunkSupplement,
) {
for y in 0..vol.size_xy().y as i32 {

View File

@ -3,6 +3,7 @@
#![allow(clippy::option_map_unit_fn)]
#![feature(
arbitrary_enum_discriminant,
bool_to_option,
const_generics,
const_panic,
label_break_value,
@ -25,6 +26,7 @@ pub mod util;
pub use crate::config::CONFIG;
pub use block::BlockGen;
pub use column::ColumnSample;
pub use index::{IndexOwned, IndexRef};
use crate::{
column::ColumnGen,
@ -32,6 +34,7 @@ use crate::{
util::{Grid, Sampler},
};
use common::{
assets::{self, Asset},
comp::{self, bird_medium, critter, quadruped_low, quadruped_medium, quadruped_small},
generation::{ChunkSupplement, EntityInfo},
msg::server::WorldMapMsg,
@ -39,7 +42,8 @@ use common::{
vol::{ReadVol, RectVolSize, Vox, WriteVol},
};
use rand::Rng;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use std::{fs::File, io::BufReader, time::Duration};
use vek::*;
#[derive(Debug)]
@ -50,35 +54,51 @@ pub enum Error {
pub struct World {
sim: sim::WorldSim,
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 {
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 index = Index::new(seed);
let civs = civ::Civs::generate(seed, &mut sim, &mut index);
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 civs(&self) -> &civ::Civs { &self.civs }
pub fn index(&self) -> &Index { &self.index }
pub fn tick(&self, _dt: Duration) {
// 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(
&self,
) -> impl Sampler<Index = (Vec2<i32>, &Index), Sample = Option<ColumnSample>> + '_ {
) -> impl Sampler<Index = (Vec2<i32>, IndexRef), Sample = Option<ColumnSample>> + '_ {
ColumnGen::new(&self.sim)
}
@ -87,6 +107,7 @@ impl World {
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
pub fn generate_chunk(
&self,
index: IndexRef,
chunk_pos: Vec2<i32>,
// TODO: misleading name
mut should_continue: impl FnMut() -> bool,
@ -97,7 +118,7 @@ impl World {
let grid_border = 4;
let zcache_grid = Grid::populate_from(
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();
@ -107,9 +128,9 @@ impl World {
.get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2)
.and_then(|zcache| zcache.as_ref())
.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 (base_z, sim_chunk) = match self
@ -153,7 +174,7 @@ impl World {
};
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| {
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;
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);
}
@ -184,13 +205,13 @@ impl World {
let mut rng = rand::thread_rng();
// Apply layers (paths, caves, etc.)
layer::apply_scatter_to(chunk_wpos2d, sample_get, &mut chunk, &self.index, sim_chunk);
layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk, &self.index);
layer::apply_caves_to(chunk_wpos2d, sample_get, &mut chunk, &self.index);
layer::apply_scatter_to(chunk_wpos2d, sample_get, &mut chunk, index, sim_chunk);
layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk, index);
layer::apply_caves_to(chunk_wpos2d, sample_get, &mut chunk, index);
// Apply site generation
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 = || {
@ -243,18 +264,13 @@ impl World {
chunk_wpos2d,
sample_get,
&chunk,
&self.index,
index,
&mut supplement,
);
// Apply site supplementary information
sim_chunk.sites.iter().for_each(|site| {
self.index.sites[*site].apply_supplement(
&mut rng,
chunk_wpos2d,
sample_get,
&mut supplement,
)
index.sites[*site].apply_supplement(&mut rng, chunk_wpos2d, sample_get, &mut supplement)
});
Ok((chunk, supplement))

View File

@ -1,7 +1,7 @@
use crate::{
column::ColumnSample,
sim::{RiverKind, WorldSim},
Index, CONFIG,
IndexRef, CONFIG,
};
use common::{
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
/// generate instead (e.g. one that just looks up the color in a cached
/// 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(
config: &MapConfig,
sampler: &WorldSim,
index: &Index,
index: IndexRef,
samples: Option<&[Option<ColumnSample>]>,
pos: Vec2<i32>,
) -> MapSample {

View File

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

View File

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

View File

@ -5,6 +5,7 @@ use crate::{
sim::WorldSim,
site::BlockMask,
util::{attempt, Grid, RandomField, Sampler, CARDINALS, DIRS},
IndexRef,
};
use common::{
assets,
@ -20,6 +21,7 @@ use core::{f32, hash::BuildHasherDefault};
use fxhash::FxHasher64;
use lazy_static::lazy_static;
use rand::prelude::*;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use vek::*;
@ -37,6 +39,11 @@ pub struct GenCtx<'a, R: Rng> {
rng: &'a mut R,
}
#[derive(Deserialize, Serialize)]
pub struct Colors {
pub stone: (u8, u8, u8),
}
const ALT_OFFSET: i32 = -2;
const LEVELS: usize = 5;
@ -80,6 +87,7 @@ impl Dungeon {
pub fn apply_to<'a>(
&'a self,
index: IndexRef,
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
@ -112,7 +120,14 @@ impl Dungeon {
.ok()
.copied()
.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)
{
@ -125,7 +140,7 @@ impl Dungeon {
for floor in &self.floors {
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() {
if let Some(block) = sampler(rz).finish() {
@ -574,16 +589,29 @@ impl Floor {
}
#[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 tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE));
let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2;
let rtile_pos = rpos - tile_center;
let colors = &index.colors.site.dungeon;
let empty = BlockMask::new(Block::empty(), 1);
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) {
stone

View File

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

View File

@ -2,17 +2,29 @@ use super::{super::skeleton::*, Archetype};
use crate::{
site::BlockMask,
util::{RandomField, Sampler},
IndexRef,
};
use common::{
make_case_elim,
terrain::{Block, BlockKind},
vol::Vox,
};
use rand::prelude::*;
use serde::{Deserialize, Serialize};
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 flag_color: Rgb<u8>,
pub stone_color: Rgb<u8>,
pub flag_color: FlagColor,
pub stone_color: StoneColor,
}
pub struct Attr {
@ -24,6 +36,24 @@ pub struct Attr {
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 {
type Attr = Attr;
@ -71,8 +101,8 @@ impl Archetype for Keep {
(
Self {
flag_color: Rgb::new(200, 80, 40),
stone_color: Rgb::new(100, 100, 110),
flag_color: FlagColor::Good,
stone_color: StoneColor::Good,
},
skel,
)
@ -81,6 +111,7 @@ impl Archetype for Keep {
#[allow(clippy::if_same_then_else)] // TODO: Pending review in #587
fn draw(
&self,
index: IndexRef,
pos: Vec3<i32>,
_dist: i32,
bound_offset: Vec2<i32>,
@ -91,6 +122,11 @@ impl Archetype for Keep {
_len: i32,
attr: &Self::Attr,
) -> 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 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 = 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(
self.stone_color.r + brick_tex,
self.stone_color.g + brick_tex,
self.stone_color.b + brick_tex,
stone_color.0 + brick_tex,
stone_color.1 + brick_tex,
stone_color.2 + brick_tex,
);
let window = BlockMask::new(
Block::new(BlockKind::Window1, make_meta(ori.flip())),
normal_layer,
);
let floor = make_block(
80 + (pos.y.abs() % 2) as u8 * 15,
60 + (pos.y.abs() % 2) as u8 * 15,
10 + (pos.y.abs() % 2) as u8 * 15,
colors.floor_base.0 + (pos.y.abs() % 2) as u8 * 15,
colors.floor_base.1 + (pos.y.abs() % 2) as u8 * 15,
colors.floor_base.2 + (pos.y.abs() % 2) as u8 * 15,
)
.with_priority(important_layer);
let pole = make_block(90, 70, 50).with_priority(important_layer);
let flag = make_block(self.flag_color.r, self.flag_color.g, self.flag_color.b)
.with_priority(important_layer);
let pole =
make_block(colors.pole.0, colors.pole.1, colors.pole.2).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 empty = BlockMask::nothing();
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) {
stone

View File

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

View File

@ -7,10 +7,17 @@ pub use self::{
skeleton::*,
};
use crate::IndexRef;
use common::terrain::Block;
use rand::prelude::*;
use serde::{Deserialize, Serialize};
use vek::*;
#[derive(Deserialize, Serialize)]
pub struct Colors {
pub archetype: archetype::Colors,
}
pub struct Building<A: Archetype> {
skel: Skeleton<A::Attr>,
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;
self.skel
.sample_closest(
rpos,
|pos, dist, bound_offset, center_offset, ori, branch| {
self.archetype.draw(
index,
pos,
dist,
bound_offset,

View File

@ -10,6 +10,7 @@ use crate::{
column::ColumnSample,
sim::WorldSim,
util::{RandomField, Sampler, StructureGen2d},
IndexRef,
};
use common::{
assets,
@ -25,9 +26,30 @@ use common::{
use fxhash::FxHasher64;
use hashbrown::{HashMap, HashSet};
use rand::prelude::*;
use serde::{Deserialize, Serialize};
use std::{collections::VecDeque, f32, hash::BuildHasherDefault};
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)]
pub fn gradient(line: [Vec2<f32>; 2]) -> f32 {
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 {
StructureKind::House(house) => house.sample(rpos),
StructureKind::Keep(keep) => keep.sample(rpos),
StructureKind::House(house) => house.sample(index, rpos),
StructureKind::Keep(keep) => keep.sample(index, rpos),
}
}
}
@ -527,10 +549,13 @@ impl Settlement {
#[allow(clippy::modulo_one)] // TODO: Pending review in #587
pub fn apply_to<'a>(
&'a self,
index: IndexRef,
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
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 x in 0..vol.size_xy().x as i32 {
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;
@ -634,9 +618,9 @@ impl Settlement {
|seed, n| self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n;
let color = match sample.plot {
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::Dirt) => Some(colors.plot_dirt.into()),
Some(Plot::Grass) => Some(colors.plot_grass.into()),
Some(Plot::Water) => Some(colors.plot_water.into()),
//Some(Plot::Town { district }) => None,
Some(Plot::Town { .. }) => {
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(
Rgb::iota(),
|e: u8, i: i32| {
e.saturating_add(
(self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 1)
as u8,
)
.saturating_sub(8)
}))
},
))
},
Some(Plot::Field { seed, crop, .. }) => {
let furrow_dirs = [
@ -677,12 +664,13 @@ impl Settlement {
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| {
let dirt = Rgb::<u8>::from(colors.plot_field_dirt).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| {
let mound = Rgb::<u8>::from(colors.plot_field_mound)
.map(|e| e + roll(0, 8) as u8)
.map(|e| {
e + (self.noise.get(Vec3::broadcast((seed % 4096 + 1) as i32))
% 32) as u8
});
@ -769,8 +757,8 @@ impl Settlement {
// Walls
if let Some((WayKind::Wall, dist, _)) = sample.way {
let color = Lerp::lerp(
Rgb::new(130i32, 100, 0),
Rgb::new(90, 70, 50),
Rgb::<u8>::from(colors.wall_low).map(i32::from),
Rgb::<u8>::from(colors.wall_high).map(i32::from),
(RandomField::new(0).get(wpos2d.into()) % 256) as f32 / 256.0,
)
.map(|e| (e % 256) as u8);
@ -797,7 +785,7 @@ impl Settlement {
for z in -2..16 {
let _ = vol.set(
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 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);
}
}
@ -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);
// match sample.tower {
// Some((Tower::Wall, _)) => return Some(Rgb::new(50, 50, 50)),
// _ => {},
// }
// match sample.way {
// Some((WayKind::Path, _, _)) => return Some(Rgb::new(90, 70, 50)),
// Some((WayKind::Hedge, _, _)) => return Some(Rgb::new(0, 150, 0)),
// Some((WayKind::Wall, _, _)) => return Some(Rgb::new(60, 60, 60)),
// _ => {},
// }
match sample.plot {
Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)),
Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)),
Some(Plot::Water) => return Some(Rgb::new(100, 150, 250)),
Some(Plot::Dirt) => return Some(colors.plot_dirt.into()),
Some(Plot::Grass) => return Some(colors.plot_grass.into()),
Some(Plot::Water) => return Some(colors.plot_water.into()),
Some(Plot::Town { .. }) => {
return Some(Rgb::new(150, 110, 60).map2(Rgb::iota(), |e: u8, i: i32| {
e.saturating_add((self.noise.get(Vec3::new(pos.x, pos.y, i * 5)) % 16) as u8)
return Some(
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)
}));
}),
);
},
Some(Plot::Field { seed, .. }) => {
let furrow_dirs = [
@ -972,6 +954,12 @@ impl Settlement {
];
let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()];
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(
if furrow {
100
@ -1003,6 +991,8 @@ pub enum Crop {
Sunflower,
}
// NOTE: No support for struct variants in make_case_elim yet, unfortunately, so
// we can't use it.
#[derive(Copy, Clone, PartialEq)]
pub enum Plot {
Hazard,

View File

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