mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
parent
5b1625f99d
commit
e2f5162e4f
@ -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
|
||||
|
152
assets/world/style/colors.ron
Normal file
152
assets/world/style/colors.ron
Normal 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.
|
||||
),
|
||||
),
|
||||
)
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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::*;
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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), ()> {
|
||||
|
@ -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),
|
||||
)
|
||||
|
@ -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(
|
||||
BlockKind::Leaves,
|
||||
Lerp::lerp(
|
||||
saturate_leaves(Rgb::new(32.0, 56.0, 22.0)),
|
||||
saturate_leaves(Rgb::new(57.0, 69.0, 27.0)),
|
||||
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())
|
||||
},
|
||||
// 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,
|
||||
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),
|
||||
)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
);
|
||||
|
||||
|
@ -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 {
|
||||
seed,
|
||||
time: 0.0,
|
||||
noise: Noise::new(seed),
|
||||
sites: Store::default(),
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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>,
|
||||
|
@ -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,
|
||||
|
@ -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| {
|
||||
e.saturating_add(
|
||||
(self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 1)
|
||||
as u8,
|
||||
)
|
||||
.saturating_sub(8)
|
||||
}))
|
||||
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,
|
||||
|
@ -1,4 +1,3 @@
|
||||
// pub mod boruvka;
|
||||
pub mod fast_noise;
|
||||
pub mod grid;
|
||||
pub mod map_vec;
|
||||
|
Loading…
Reference in New Issue
Block a user