Merge branch 'zesterer/small-fixes' into 'master'

Caverns

See merge request veloren/veloren!2763
This commit is contained in:
Joshua Barretto 2021-10-16 09:33:57 +00:00
commit 2da9f7994f
48 changed files with 875 additions and 163 deletions

1
Cargo.lock generated
View File

@ -6275,6 +6275,7 @@ dependencies = [
"ordered-float 2.8.0",
"profiling",
"rand 0.8.4",
"rand_chacha 0.3.1",
"rayon",
"rodio",
"ron",

View File

@ -131,6 +131,9 @@ void main() {
vec3 emitted_light, reflected_light;
// Prevent the sky affecting light when underground
float not_underground = clamp((f_pos.z - f_alt) / 128.0 + 1.0, 0.0, 1.0);
// float point_shadow = shadow_at(f_pos, f_norm);
// vec3 cam_to_frag = normalize(f_pos - cam_pos.xyz);
// vec3 emitted_light, reflected_light;
@ -143,6 +146,11 @@ void main() {
// vec3 surf_color = /*srgb_to_linear*/(vec3(0.4, 0.7, 2.0));
float max_light = 0.0;
max_light += get_sun_diffuse2(sun_info, moon_info, f_norm, /*time_of_day.x*//*-cam_to_frag*/sun_view_dir/*view_dir*/, f_pos, mu, cam_attenuation, fluid_alt, k_a/* * (shade_frac * 0.5 + light_frac * 0.5)*/, /*vec3(0.0)*/k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light);
emitted_light *= not_underground;
reflected_light *= not_underground;
// Global illumination when underground (silly)
emitted_light += (1.0 - not_underground) * 0.05;
// reflected_light *= f_light * point_shadow * shade_frac;
// emitted_light *= f_light * point_shadow * max(shade_frac, MIN_SHADOW);
// max_light *= f_light * point_shadow * shade_frac;

View File

@ -179,6 +179,10 @@ void main() {
vec3 reflect_color = get_sky_color(/*reflect_ray_dir*/beam_view_dir, time_of_day.x, f_pos, vec3(-100000), 0.125, true);
reflect_color = get_cloud_color(reflect_color, reflect_ray_dir, cam_pos.xyz, time_of_day.x, 100000.0, 0.1);
reflect_color *= f_light;
// Prevent the sky affecting light when underground
float not_underground = clamp((f_pos.z - f_alt) / 128.0 + 1.0, 0.0, 1.0);
reflect_color *= not_underground;
// /*const */vec3 water_color = srgb_to_linear(vec3(0.2, 0.5, 1.0));
// /*const */vec3 water_color = srgb_to_linear(vec3(0.8, 0.9, 1.0));
// NOTE: Linear RGB, attenuation coefficients for water at roughly R, G, B wavelengths.
@ -254,6 +258,11 @@ void main() {
float max_light = 0.0;
max_light += get_sun_diffuse2(sun_info, moon_info, norm, /*time_of_day.x*/sun_view_dir, f_pos, mu, cam_attenuation, fluid_alt, k_a/* * (shade_frac * 0.5 + light_frac * 0.5)*/, vec3(k_d), /*vec3(f_light * point_shadow)*//*reflect_color*/k_s, alpha, f_norm, 1.0, emitted_light, reflected_light);
emitted_light *= not_underground;
reflected_light *= not_underground;
// Global illumination when underground (silly)
emitted_light += (1.0 - not_underground) * 0.05;
// Apply cloud layer to sky
// reflected_light *= /*water_color_direct * */reflect_color * f_light * point_shadow * shade_frac;
// emitted_light *= /*water_color_direct*//*ambient_attenuation * */f_light * point_shadow * max(shade_frac, MIN_SHADOW);

View File

@ -16,7 +16,7 @@ float cloud_broad(vec3 pos) {
}
// Returns vec4(r, g, b, density)
vec4 cloud_at(vec3 pos, float dist, out vec3 emission) {
vec4 cloud_at(vec3 pos, float dist, out vec3 emission, out float not_underground) {
// Natural attenuation of air (air naturally attenuates light that passes through it)
// Simulate the atmosphere thinning as you get higher. Not physically accurate, but then
// it can't be since Veloren's world is flat, not spherical.
@ -134,7 +134,7 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission) {
//moon_access *= suppress_mist;
// Prevent clouds and mist appearing underground (but fade them out gently)
float not_underground = clamp(1.0 - (alt - (pos.z - focus_off.z)) / 80.0 + dist * 0.001, 0, 1);
not_underground = clamp(1.0 - (alt - (pos.z - focus_off.z)) / 80.0 + dist * 0.001, 0, 1);
sun_access *= not_underground;
moon_access *= not_underground;
float vapor_density = (mist + cloud) * not_underground;
@ -220,8 +220,9 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
cdist = step_to_dist(trunc(dist_to_step(cdist - 0.25, quality)), quality);
vec3 emission;
float not_underground; // Used to prevent sunlight leaking underground
// `sample` is a reserved keyword
vec4 sample_ = cloud_at(origin + dir * ldist * splay, ldist, emission);
vec4 sample_ = cloud_at(origin + dir * ldist * splay, ldist, emission, not_underground);
vec2 density_integrals = max(sample_.zw, vec2(0));
@ -239,7 +240,7 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
// Add the directed light light scattered into the camera by the clouds and the atmosphere (global illumination)
sun_color * sun_scatter * get_sun_brightness() * (sun_access * (1.0 - cloud_darken) /*+ sky_color * global_scatter_factor*/) +
moon_color * moon_scatter * get_moon_brightness() * (moon_access * (1.0 - cloud_darken) /*+ sky_color * global_scatter_factor*/) +
sky_light * (1.0 - global_darken) +
sky_light * (1.0 - global_darken) * not_underground +
emission * density_integrals.y;
}

View File

@ -57,7 +57,7 @@ vec3 glow_light(vec3 pos) {
#if (SHADOW_MODE <= SHADOW_MODE_NONE)
return GLOW_COLOR;
#else
return GLOW_COLOR * (1.0 + (noise_3d(vec3(pos.xy * 0.005, tick.x * 0.5)) - 0.5) * 1.0);
return GLOW_COLOR * (1.0 + (noise_3d(vec3(pos.xy * 0.005, tick.x * 0.5)) - 0.5) * 0.5);
#endif
}

View File

@ -253,8 +253,12 @@ void main() {
// Computing light attenuation from water.
vec3 emitted_light, reflected_light;
// Prevent the sky affecting light when underground
float not_underground = clamp((f_pos.z - f_alt) / 128.0 + 1.0, 0.0, 1.0);
// To account for prior saturation
/*float */f_light = faces_fluid ? 1.0 : f_light * sqrt(f_light);
/*float */f_light = faces_fluid ? not_underground : f_light * sqrt(f_light);
emitted_light = vec3(1.0);
reflected_light = vec3(1.0);
@ -268,7 +272,7 @@ void main() {
max_light *= f_light;
// TODO: Apply AO after this
vec3 glow = glow_light(f_pos) * (pow(f_glow, 6) * 5 + pow(f_glow, 1.5) * 2);
vec3 glow = glow_light(f_pos) * (pow(f_glow, 3) * 5 + pow(f_glow, 2.0) * 2);
reflected_light += glow;
max_light += lights_at(f_pos, f_norm, view_dir, mu, cam_attenuation, fluid_alt, k_a, k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light);

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -3258,6 +3258,7 @@ CookingPot: Some((
],
wind_sway: 0.0,
)),
// Ensnaring Vines
EnsnaringVines: Some((
variations: [
(
@ -3310,4 +3311,164 @@ Bones: Some((
],
wind_sway: 0.0,
)),
// Short Cavern Grass Blue
CavernGrassBlueShort: Some((
variations: [
(
model: "voxygen.voxel.sprite.cavern.grass_short-0",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_short-1",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_short-2",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_short-3",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_short-4",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
],
wind_sway: 0.0,
)),
// Medium Cavern Grass Blue
CavernGrassBlueMedium: Some((
variations: [
(
model: "voxygen.voxel.sprite.cavern.grass_med-0",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_med-1",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_med-2",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_med-3",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
],
wind_sway: 0.0,
)),
// Long Cavern Grass Blue
CavernGrassBlueLong: Some((
variations: [
(
model: "voxygen.voxel.sprite.cavern.grass_long-0",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-1",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-2",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-3",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-4",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-5",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-6",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-7",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
],
wind_sway: 0.0,
)),
// Cavern Lillypads Blue
CavernLillypadBlue: Some((
variations: [
(
model: "voxygen.voxel.sprite.cavern.lillypad-0",
offset: (-5.5, -5.5, -1.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.lillypad-1",
offset: (-5.5, -5.5, -1.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.lillypad-2",
offset: (-5.5, -5.5, -1.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.lillypad-3",
offset: (-5.5, -5.5, -1.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.lillypad-4",
offset: (-5.5, -5.5, -1.0),
lod_axes: (0.0, 0.0, 0.0),
),
],
wind_sway: 0.0,
)),
// Cavern Hanging Mycel Blue
CavernMycelBlue: Some((
variations: [
(
model: "voxygen.voxel.sprite.cavern.mycel-0",
offset: (-0.5, -0.5, -21.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.mycel-1",
offset: (-0.5, -0.5, -31.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.mycel-2",
offset: (-0.5, -0.5, -14.0),
lod_axes: (0.0, 0.0, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.mycel-3",
offset: (-0.5, -0.5, -40.0),
lod_axes: (0.0, 0.0, 0.0),
),
],
wind_sway: 0.1,
)),
)

12
assets/world/features.ron Normal file
View File

@ -0,0 +1,12 @@
#![enable(unwrap_newtypes)]
#![enable(implicit_some)]
(
caverns: false, // TODO: Disabled by default until cave overhaul
caves: true,
shrubs: true,
trees: true,
scatter: true,
paths: true,
spots: true,
)

View File

@ -67,7 +67,7 @@
deep_stone_color: (125, 120, 130),
layer: (
bridge: (80, 80, 100),
stalagtite: (90, 71, 112),
stalactite: (90, 71, 112),
cave_floor: (42, 39, 82),
cave_roof: (38, 21, 79),
dirt: (69, 48, 15),

View File

@ -644,6 +644,11 @@ impl<const AVERAGE_PALETTE: bool> VoxelImageDecoding for TriPngEncoding<AVERAGE_
g: 206,
b: 64,
},
GlowingMushroom => Rgb {
r: 50,
g: 250,
b: 250,
},
Misc => Rgb {
r: 255,
g: 0,

View File

@ -48,7 +48,8 @@ make_case_elim!(
// 0x32 <= x < 0x40 is reserved for future earths/muds/gravels/sands/etc.
Wood = 0x40,
Leaves = 0x41,
// 0x42 <= x < 0x50 is reserved for future tree parts
GlowingMushroom = 0x42,
// 0x43 <= x < 0x50 is reserved for future tree parts
// Covers all other cases (we sometimes have bizarrely coloured misc blocks, and also we
// often want to experiment with new kinds of block without allocating them a
// dedicated block kind.
@ -177,7 +178,8 @@ impl Block {
pub fn get_glow(&self) -> Option<u8> {
match self.kind() {
BlockKind::Lava => Some(24),
BlockKind::GlowingRock | BlockKind::GlowingWeakRock => Some(12),
BlockKind::GlowingRock | BlockKind::GlowingWeakRock => Some(10),
BlockKind::GlowingMushroom => Some(20),
_ => match self.get_sprite()? {
SpriteKind::StreetLamp | SpriteKind::StreetLampTall => Some(24),
SpriteKind::Ember => Some(20),
@ -188,7 +190,11 @@ impl Block {
| SpriteKind::Orb => Some(16),
SpriteKind::Velorite
| SpriteKind::VeloriteFrag
| SpriteKind::Cauldron
| SpriteKind::CavernGrassBlueShort
| SpriteKind::CavernGrassBlueMedium
| SpriteKind::CavernGrassBlueLong
| SpriteKind::CavernLillypadBlue
| SpriteKind::CavernMycelBlue
| SpriteKind::CeilingMushroom => Some(6),
SpriteKind::CaveMushroom
| SpriteKind::CookingPot

View File

@ -71,6 +71,22 @@ impl<V, S: RectVolSize, M: Clone> Chonk<V, S, M> {
self.sub_chunks.iter().map(SubChunk::num_groups).sum()
}
/// Iterate through the voxels in this chunk, attempting to avoid those that
/// are unchanged (i.e: match the `below` and `above` voxels). This is
/// generally useful for performance reasons.
pub fn iter_changed(&self) -> impl Iterator<Item = (Vec3<i32>, &V)> + '_ {
self.sub_chunks
.iter()
.enumerate()
.filter(|(_, sc)| sc.num_groups() > 0)
.map(move |(i, sc)| {
let z_offset = self.z_offset + i as i32 * SubChunkSize::<S>::SIZE.z as i32;
sc.vol_iter(Vec3::zero(), SubChunkSize::<S>::SIZE.map(|e| e as i32))
.map(move |(pos, vox)| (pos + Vec3::unit_z() * z_offset, vox))
})
.flatten()
}
// Returns the index (in self.sub_chunks) of the SubChunk that contains
// layer z; note that this index changes when more SubChunks are prepended
#[inline]

View File

@ -177,6 +177,11 @@ make_case_elim!(
WitchWindow = 0x96,
SmokeDummy = 0x97,
Bones = 0x98,
CavernGrassBlueShort = 0x99,
CavernGrassBlueMedium = 0x9A,
CavernGrassBlueLong = 0x9B,
CavernLillypadBlue = 0x9C,
CavernMycelBlue = 0x9D,
}
);
@ -263,7 +268,7 @@ impl SpriteKind {
| SpriteKind::Tin
| SpriteKind::Silver
| SpriteKind::Gold => 0.6,
SpriteKind::EnsnaringVines => 0.1,
SpriteKind::EnsnaringVines | SpriteKind::CavernLillypadBlue => 0.1,
_ => return None,
})
}

View File

@ -851,7 +851,7 @@ impl Server {
let ecs = self.state.ecs_mut();
let slow_jobs = ecs.write_resource::<SlowJobPool>();
index.reload_colors_if_changed(|index| {
index.reload_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>();

View File

@ -106,6 +106,7 @@ native-dialog = { version = "0.5.2", optional = true }
num = "0.4"
ordered-float = { version = "2.0.1", default-features = false }
rand = "0.8"
rand_chacha = "0.3"
rayon = "1.5"
rodio = {version = "0.14", default-features = false, features = ["vorbis"]}
ron = {version = "0.6", default-features = false}

View File

@ -1,10 +1,8 @@
use crate::hud::CraftingTab;
use common::{
terrain::{BlockKind, SpriteKind, TerrainChunk},
vol::{IntoVolIterator, RectRasterableVol},
};
use common::terrain::{BlockKind, SpriteKind, TerrainChunk};
use common_base::span;
use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
use vek::*;
#[derive(Copy, Clone, Debug)]
@ -60,106 +58,94 @@ impl BlocksOfInterest {
let mut cricket3 = Vec::new();
let mut frogs = Vec::new();
chunk
.vol_iter(
Vec3::new(0, 0, chunk.get_min_z()),
Vec3::new(
TerrainChunk::RECT_SIZE.x as i32,
TerrainChunk::RECT_SIZE.y as i32,
chunk.get_max_z(),
),
)
.for_each(|(pos, block)| {
match block.kind() {
BlockKind::Leaves if thread_rng().gen_range(0..16) == 0 => leaves.push(pos),
BlockKind::WeakRock if thread_rng().gen_range(0..6) == 0 => drip.push(pos),
BlockKind::Grass => {
if thread_rng().gen_range(0..16) == 0 {
grass.push(pos);
}
match thread_rng().gen_range(0..8192) {
1 => cricket1.push(pos),
2 => cricket2.push(pos),
3 => cricket3.push(pos),
_ => {},
}
},
BlockKind::Water
if chunk.meta().contains_river() && thread_rng().gen_range(0..16) == 0 =>
{
river.push(pos)
},
BlockKind::Lava if thread_rng().gen_range(0..5) == 0 => {
fires.push(pos + Vec3::unit_z())
},
BlockKind::Snow if thread_rng().gen_range(0..16) == 0 => snow.push(pos),
_ => match block.get_sprite() {
Some(SpriteKind::Ember) => {
fires.push(pos);
smokers.push(pos);
},
Some(SpriteKind::SmokeDummy) => {
smokers.push(pos);
},
// Offset positions to account for block height.
// TODO: Is this a good idea?
Some(SpriteKind::StreetLamp) => fire_bowls.push(pos + Vec3::unit_z() * 2),
Some(SpriteKind::FireBowlGround) => fire_bowls.push(pos + Vec3::unit_z()),
Some(SpriteKind::StreetLampTall) => {
fire_bowls.push(pos + Vec3::unit_z() * 3);
},
Some(SpriteKind::WallSconce) => fire_bowls.push(pos + Vec3::unit_z()),
Some(SpriteKind::Beehive) => beehives.push(pos),
Some(SpriteKind::CrystalHigh) => fireflies.push(pos),
Some(SpriteKind::Reed) => {
reeds.push(pos);
fireflies.push(pos);
if thread_rng().gen_range(0..12) == 0 {
frogs.push(pos);
}
},
Some(SpriteKind::CaveMushroom) => fireflies.push(pos),
Some(SpriteKind::PinkFlower) => flowers.push(pos),
Some(SpriteKind::PurpleFlower) => flowers.push(pos),
Some(SpriteKind::RedFlower) => flowers.push(pos),
Some(SpriteKind::WhiteFlower) => flowers.push(pos),
Some(SpriteKind::YellowFlower) => flowers.push(pos),
Some(SpriteKind::Sunflower) => flowers.push(pos),
Some(SpriteKind::CraftingBench) => {
interactables.push((pos, Interaction::Craft(CraftingTab::All)))
},
Some(SpriteKind::Forge) => {
interactables.push((pos, Interaction::Craft(CraftingTab::Dismantle)))
},
Some(SpriteKind::TanningRack) => interactables
.push((pos, Interaction::Craft(CraftingTab::ProcessedMaterial))),
Some(SpriteKind::SpinningWheel) => {
interactables.push((pos, Interaction::Craft(CraftingTab::All)))
},
Some(SpriteKind::Loom) => {
interactables.push((pos, Interaction::Craft(CraftingTab::All)))
},
Some(SpriteKind::Cauldron) => {
fires.push(pos);
interactables.push((pos, Interaction::Craft(CraftingTab::Potion)))
},
Some(SpriteKind::Anvil) => {
interactables.push((pos, Interaction::Craft(CraftingTab::Weapon)))
},
Some(SpriteKind::CookingPot) => {
fires.push(pos);
interactables.push((pos, Interaction::Craft(CraftingTab::Food)))
},
let mut rng = ChaCha8Rng::from_seed(thread_rng().gen());
chunk.iter_changed().for_each(|(pos, block)| {
match block.kind() {
BlockKind::Leaves if rng.gen_range(0..16) == 0 => leaves.push(pos),
BlockKind::WeakRock if rng.gen_range(0..6) == 0 => drip.push(pos),
BlockKind::Grass => {
if rng.gen_range(0..16) == 0 {
grass.push(pos);
}
match rng.gen_range(0..8192) {
1 => cricket1.push(pos),
2 => cricket2.push(pos),
3 => cricket3.push(pos),
_ => {},
}
},
BlockKind::Water if chunk.meta().contains_river() && rng.gen_range(0..16) == 0 => {
river.push(pos)
},
BlockKind::Snow if rng.gen_range(0..16) == 0 => snow.push(pos),
BlockKind::Lava if rng.gen_range(0..5) == 0 => fires.push(pos + Vec3::unit_z()),
BlockKind::Snow if rng.gen_range(0..16) == 0 => snow.push(pos),
_ => match block.get_sprite() {
Some(SpriteKind::Ember) => {
fires.push(pos);
smokers.push(pos);
},
}
if block.is_collectible() {
interactables.push((pos, Interaction::Collect));
}
if let Some(glow) = block.get_glow() {
lights.push((pos, glow));
}
});
// Offset positions to account for block height.
// TODO: Is this a good idea?
Some(SpriteKind::StreetLamp) => fire_bowls.push(pos + Vec3::unit_z() * 2),
Some(SpriteKind::FireBowlGround) => fire_bowls.push(pos + Vec3::unit_z()),
Some(SpriteKind::StreetLampTall) => fire_bowls.push(pos + Vec3::unit_z() * 4),
Some(SpriteKind::WallSconce) => fire_bowls.push(pos + Vec3::unit_z()),
Some(SpriteKind::Beehive) => beehives.push(pos),
Some(SpriteKind::CrystalHigh) => fireflies.push(pos),
Some(SpriteKind::Reed) => {
reeds.push(pos);
fireflies.push(pos);
if rng.gen_range(0..12) == 0 {
frogs.push(pos);
}
},
Some(SpriteKind::CaveMushroom) => fireflies.push(pos),
Some(SpriteKind::PinkFlower) => flowers.push(pos),
Some(SpriteKind::PurpleFlower) => flowers.push(pos),
Some(SpriteKind::RedFlower) => flowers.push(pos),
Some(SpriteKind::WhiteFlower) => flowers.push(pos),
Some(SpriteKind::YellowFlower) => flowers.push(pos),
Some(SpriteKind::Sunflower) => flowers.push(pos),
Some(SpriteKind::CraftingBench) => {
interactables.push((pos, Interaction::Craft(CraftingTab::All)))
},
Some(SpriteKind::SmokeDummy) => {
smokers.push(pos);
},
Some(SpriteKind::Forge) => {
interactables.push((pos, Interaction::Craft(CraftingTab::Dismantle)))
},
Some(SpriteKind::TanningRack) => interactables
.push((pos, Interaction::Craft(CraftingTab::ProcessedMaterial))),
Some(SpriteKind::SpinningWheel) => {
interactables.push((pos, Interaction::Craft(CraftingTab::All)))
},
Some(SpriteKind::Loom) => {
interactables.push((pos, Interaction::Craft(CraftingTab::All)))
},
Some(SpriteKind::Cauldron) => {
fires.push(pos);
interactables.push((pos, Interaction::Craft(CraftingTab::Potion)))
},
Some(SpriteKind::Anvil) => {
interactables.push((pos, Interaction::Craft(CraftingTab::Weapon)))
},
Some(SpriteKind::CookingPot) => {
fires.push(pos);
interactables.push((pos, Interaction::Craft(CraftingTab::Food)))
},
_ => {},
},
}
if block.is_collectible() {
interactables.push((pos, Interaction::Collect));
}
if let Some(glow) = block.get_glow() {
lights.push((pos, glow));
}
});
Self {
leaves,

View File

@ -17,7 +17,12 @@ fn main() {
let mut focus = Vec2::<f32>::zero();
let mut zoom = 1.0;
let colors = &**index.colors().read();
let index = IndexRef { colors, index };
let features = &**index.features().read();
let index = IndexRef {
colors,
features,
index,
};
while win.is_open() {
let mut buf = vec![0; W * H];

View File

@ -414,7 +414,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
downhill_chunk.river.river_kind,
Some(RiverKind::Lake { .. })
))
&& (river_chunk.water_alt > downhill_chunk.water_alt + 8.0)
&& (river_chunk.water_alt > downhill_chunk.water_alt + 0.0)
}
/// Determine the altitude of a river based on the altitude of the
@ -452,7 +452,14 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
// they do not result in artifacts, even in edge cases. The exact configuration
// of this code is the product of hundreds of hours of testing and
// refinement and I ask that you do not take that effort lightly.
let (river_water_level, in_river, lake_water_level, lake_dist, water_dist, unbounded_water_level) = neighbor_river_data.iter().copied().fold(
let (
river_water_level,
in_river,
lake_water_level,
lake_dist,
water_dist,
unbounded_water_level,
) = neighbor_river_data.iter().copied().fold(
(
WeightedSum::default().with_max(base_sea_level),
false,
@ -461,7 +468,18 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
None,
WeightedSum::default().with_max(base_sea_level),
),
|(mut river_water_level, mut in_river, mut lake_water_level, mut lake_dist, water_dist, mut unbounded_water_level), (river_chunk_idx, river_chunk, river, dist_info)| match (river.river_kind, dist_info) {
|(
mut river_water_level,
mut in_river,
lake_water_level,
mut lake_dist,
water_dist,
mut unbounded_water_level,
),
(river_chunk_idx, river_chunk, river, dist_info)| match (
river.river_kind,
dist_info,
) {
(
Some(kind),
Some((_, _, river_width, (river_t, (river_pos, _), downhill_chunk))),
@ -480,7 +498,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
match kind {
RiverKind::River { .. } => {
// Alt of river water *is* the alt of land (ignoring gorge, which gets applied later)
// Alt of river water *is* the alt of land (ignoring gorge, which gets
// applied later)
let river_water_alt = river_water_alt(
river_chunk.alt.max(river_chunk.water_alt),
downhill_chunk.alt.max(downhill_chunk.water_alt),
@ -488,14 +507,15 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
is_waterfall(river_chunk_idx, river_chunk, downhill_chunk),
);
river_water_level = river_water_level
.with(river_water_alt, near_center);
river_water_level =
river_water_level.with(river_water_alt, near_center);
if river_edge_dist <= 0.0 {
in_river = true;
}
},
// Slightly wider threshold is chosen in case the lake bounds are a bit wrong
// Slightly wider threshold is chosen in case the lake bounds are a bit
// wrong
RiverKind::Lake { .. } | RiverKind::Ocean => {
let lake_water_alt = if matches!(kind, RiverKind::Ocean) {
base_sea_level
@ -509,32 +529,63 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
};
if river_edge_dist > 0.0 && river_width > lake_width * 0.99 {
let unbounded_water_alt = lake_water_alt - ((river_edge_dist - 8.0).max(0.0) / 5.0).powf(2.0);
let unbounded_water_alt = lake_water_alt
- ((river_edge_dist - 8.0).max(0.0) / 5.0).powf(2.0);
unbounded_water_level = unbounded_water_level
.with(unbounded_water_alt, 1.0 / (1.0 + river_edge_dist * 5.0))
.with_max(unbounded_water_alt);
.with(unbounded_water_alt, 1.0 / (1.0 + river_edge_dist * 5.0));
//.with_max(unbounded_water_alt);
}
river_water_level = river_water_level
.with(lake_water_alt, near_center);
river_water_level = river_water_level.with(lake_water_alt, near_center);
lake_dist = lake_dist.min(river_edge_dist);
// Lake border prevents a lake failing to propagate its altitude to nearby rivers
if river_edge_dist <= 0.0 {
lake_water_level = lake_water_level
// Make sure the closest lake is prioritised
.with(lake_water_alt, near_center + 0.1 / (1.0 + river_edge_dist));
// Lake border prevents a lake failing to propagate its altitude to
// nearby rivers
let off = 0.0;
let len = 3.0;
if river_edge_dist <= off {
// lake_water_level = lake_water_level
// // Make sure the closest lake is prioritised
// .with(lake_water_alt, near_center + 0.1 / (1.0 +
// river_edge_dist)); // .with_min(lake_water_alt);
//
river_water_level = river_water_level.with_min(
lake_water_alt
+ ((((river_dist - river_width * 0.5) as f32 + len - off)
.max(0.0))
/ len)
.powf(1.5)
* 32.0,
);
}
},
};
let river_edge_dist_unclamped = (river_dist - river_width * 0.5) as f32;
let water_dist = Some(water_dist.unwrap_or(river_edge_dist_unclamped).min(river_edge_dist_unclamped));
let water_dist = Some(
water_dist
.unwrap_or(river_edge_dist_unclamped)
.min(river_edge_dist_unclamped),
);
(river_water_level, in_river, lake_water_level, lake_dist, water_dist, unbounded_water_level)
(
river_water_level,
in_river,
lake_water_level,
lake_dist,
water_dist,
unbounded_water_level,
)
},
(_, _) => (river_water_level, in_river, lake_water_level, lake_dist, water_dist, unbounded_water_level),
(_, _) => (
river_water_level,
in_river,
lake_water_level,
lake_dist,
water_dist,
unbounded_water_level,
),
},
);
let unbounded_water_level = unbounded_water_level.eval_or(base_sea_level);
@ -679,7 +730,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
)
.with_min(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt))
} else {
const GORGE: f32 = 0.5;
const GORGE: f32 = 0.25;
const BANK_SCALE: f32 = 24.0;
// Weighting of this riverbank on nearby terrain (higher when closer to
// the river). This 'pulls' the riverbank

View File

@ -1,3 +1,6 @@
use common::assets;
use serde::Deserialize;
pub struct Config {
pub sea_level: f32,
pub mountain_scale: f32,
@ -69,3 +72,20 @@ pub const CONFIG: Config = Config {
river_min_height: 0.25,
river_width_to_depth: 8.0,
};
#[derive(Deserialize)]
pub struct Features {
pub caverns: bool,
pub caves: bool,
pub shrubs: bool,
pub trees: bool,
pub scatter: bool,
pub paths: bool,
pub spots: bool,
}
impl assets::Asset for Features {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}

View File

@ -1,7 +1,7 @@
use crate::{
layer::wildlife::{self, DensityFn, SpawnEntry},
site::{economy::TradeInformation, Site},
Colors,
Colors, Features,
};
use common::{
assets::{AssetExt, AssetHandle},
@ -13,6 +13,7 @@ use noise::{Seedable, SuperSimplex};
use std::sync::Arc;
const WORLD_COLORS_MANIFEST: &str = "world.style.colors";
const WORLD_FEATURES_MANIFEST: &str = "world.features";
pub struct Index {
pub seed: u32,
@ -22,6 +23,7 @@ pub struct Index {
pub trade: TradeInformation,
pub wildlife_spawns: Vec<(AssetHandle<SpawnEntry>, DensityFn)>,
colors: AssetHandle<Arc<Colors>>,
features: AssetHandle<Arc<Features>>,
}
/// An owned reference to indexed data.
@ -32,6 +34,7 @@ pub struct Index {
#[derive(Clone)]
pub struct IndexOwned {
colors: Arc<Colors>,
features: Arc<Features>,
index: Arc<Index>,
}
@ -47,6 +50,7 @@ impl Deref for IndexOwned {
#[derive(Clone, Copy)]
pub struct IndexRef<'a> {
pub colors: &'a Colors,
pub features: &'a Features,
pub index: &'a Index,
}
@ -60,6 +64,7 @@ impl Index {
/// NOTE: Panics if the color manifest cannot be loaded.
pub fn new(seed: u32) -> Self {
let colors = Arc::<Colors>::load_expect(WORLD_COLORS_MANIFEST);
let features = Arc::<Features>::load_expect(WORLD_FEATURES_MANIFEST);
let wildlife_spawns = wildlife::spawn_manifest()
.into_iter()
.map(|(e, f)| (SpawnEntry::load_expect(e), f))
@ -73,11 +78,14 @@ impl Index {
trade: Default::default(),
wildlife_spawns,
colors,
features,
}
}
pub fn colors(&self) -> AssetHandle<Arc<Colors>> { self.colors }
pub fn features(&self) -> AssetHandle<Arc<Features>> { self.features }
pub fn get_site_prices(&self, site_id: SiteId) -> Option<SitePrices> {
self.sites
.recreate_id(site_id)
@ -89,10 +97,12 @@ impl Index {
impl IndexOwned {
pub fn new(index: Index) -> Self {
let colors = index.colors.cloned();
let features = index.features.cloned();
Self {
index: Arc::new(index),
colors,
features,
}
}
@ -103,13 +113,12 @@ impl IndexOwned {
/// 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.index.colors.reloaded_global().then(move || {
// Reload the color from the asset handle, which is updated automatically
pub fn reload_if_changed<R>(&mut self, reload: impl FnOnce(&mut Self) -> R) -> Option<R> {
let reloaded = self.index.colors.reloaded_global() || self.index.features.reloaded_global();
reloaded.then(move || {
// Reload the fields from the asset handle, which is updated automatically
self.colors = self.index.colors.cloned();
self.features = self.index.features.cloned();
reload(self)
})
}
@ -117,6 +126,7 @@ impl IndexOwned {
pub fn as_index_ref(&self) -> IndexRef {
IndexRef {
colors: &self.colors,
features: &self.features,
index: &self.index,
}
}

View File

@ -10,7 +10,8 @@ pub use self::{
use crate::{
column::ColumnSample,
util::{FastNoise, RandomField, Sampler},
config::CONFIG,
util::{FastNoise, RandomField, RandomPerm, Sampler},
Canvas, IndexRef,
};
use common::{
@ -20,19 +21,20 @@ use common::{
terrain::{Block, BlockKind, SpriteKind},
vol::{BaseVol, ReadVol, RectSizedVol, WriteVol},
};
use hashbrown::HashMap;
use noise::NoiseFn;
use rand::prelude::*;
use serde::Deserialize;
use std::{
f32,
ops::{Mul, Range, Sub},
ops::{Add, Mul, Range, Sub},
};
use vek::*;
#[derive(Deserialize)]
pub struct Colors {
pub bridge: (u8, u8, u8),
pub stalagtite: (u8, u8, u8),
pub stalactite: (u8, u8, u8),
pub cave_floor: (u8, u8, u8),
pub cave_roof: (u8, u8, u8),
pub dirt: (u8, u8, u8),
@ -170,11 +172,11 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
let floor_dist = pit_condition as i32 * pit_depth as i32;
let vein_condition =
cave_depth % 12.0 > 11.5 && cave_x > 0.1 && cave_x < 0.6 && cave_depth > 200.0;
let stalagtite_condition = cave_depth > 150.0;
let stalactite_condition = cave_depth > 150.0;
let vein_depth = 3;
let vein_floor = cave_base - vein_depth;
// Stalagtites
let stalagtites = info
let stalactites = info
.index()
.noise
.cave_nz
@ -188,18 +190,18 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
)
.mul(45.0) as i32;
// Generate stalagtites if there's something for them to hold on to
// Generate stalactites if there's something for them to hold on to
if canvas
.get(Vec3::new(wpos2d.x, wpos2d.y, cave_roof))
.is_filled()
&& stalagtite_condition
&& stalactite_condition
{
for z in cave_roof - stalagtites..cave_roof {
for z in cave_roof - stalactites..cave_roof {
canvas.set(
Vec3::new(wpos2d.x, wpos2d.y, z),
Block::new(
BlockKind::WeakRock,
noisy_color(info.index().colors.layer.stalagtite.into(), 8),
noisy_color(info.index().colors.layer.stalactite.into(), 8),
),
);
}
@ -570,3 +572,396 @@ pub fn apply_coral_to(canvas: &mut Canvas) {
}
});
}
pub fn apply_caverns_to<R: Rng>(canvas: &mut Canvas, dynamic_rng: &mut R) {
let info = canvas.info();
let canvern_nz_at = |wpos2d: Vec2<i32>| {
// Horizontal average scale of caverns
let scale = 2048.0;
// How common should they be? (0.0 - 1.0)
let common = 0.15;
let cavern_nz = info
.index()
.noise
.cave_nz
.get((wpos2d.map(|e| e as f64) / scale).into_array()) as f32;
((cavern_nz * 0.5 + 0.5 - (1.0 - common)).max(0.0) / common).powf(common * 2.0)
};
// Get cavern attributes at a position
let cavern_at = |wpos2d| {
let alt = info.land().get_alt_approx(wpos2d);
// Range of heights for the caverns
let height_range = 16.0..250.0;
// Minimum distance below the surface
let surface_clearance = 64.0;
let cavern_avg_height = Lerp::lerp(
height_range.start,
height_range.end,
info.index()
.noise
.cave_nz
.get((wpos2d.map(|e| e as f64) / 300.0).into_array()) as f32
* 0.5
+ 0.5,
);
let cavern_avg_alt =
CONFIG.sea_level.min(alt * 0.25) - height_range.end - surface_clearance;
let cavern = canvern_nz_at(wpos2d);
let cavern_height = cavern * cavern_avg_height;
// Stalagtites
let stalactite = info
.index()
.noise
.cave_nz
.get(wpos2d.map(|e| e as f64 * 0.015).into_array())
.sub(0.5)
.max(0.0)
.mul((cavern_height as f64 - 5.0).mul(0.15).clamped(0.0, 1.0))
.mul(32.0 + cavern_avg_height as f64);
let hill = info
.index()
.noise
.cave_nz
.get((wpos2d.map(|e| e as f64) / 96.0).into_array()) as f32
* cavern
* 24.0;
let rugged = 0.4; // How bumpy should the floor be relative to the ceiling?
let cavern_bottom = (cavern_avg_alt - cavern_height * rugged + hill) as i32;
let cavern_avg_bottom =
(cavern_avg_alt - ((height_range.start + height_range.end) * 0.5) * rugged) as i32;
let cavern_top = (cavern_avg_alt + cavern_height) as i32;
let cavern_avg_top = (cavern_avg_alt + cavern_avg_height) as i32;
// Stalagmites rise up to meet stalactites
let stalagmite = stalactite;
let floor = stalagmite as i32;
(
cavern_bottom,
cavern_top,
cavern_avg_bottom,
cavern_avg_top,
floor,
stalactite,
cavern_avg_bottom as i32 + 16, // Water level
)
};
let mut mushroom_cache = HashMap::new();
struct Mushroom {
pos: Vec3<i32>,
stalk: f32,
head_color: Rgb<u8>,
}
// Get mushroom block, if any, at a position
let mut get_mushroom = |wpos: Vec3<i32>, dynamic_rng: &mut R| {
for (wpos2d, seed) in info.chunks().gen_ctx.structure_gen.get(wpos.xy()) {
let mushroom = if let Some(mushroom) =
mushroom_cache.entry(wpos2d).or_insert_with(|| {
let mut rng = RandomPerm::new(seed);
let (cavern_bottom, cavern_top, _, _, floor, _, water_level) =
cavern_at(wpos2d);
let pos = wpos2d.with_z(cavern_bottom + floor);
if rng.gen_bool(0.15)
&& cavern_top - cavern_bottom > 32
&& pos.z as i32 > water_level - 2
{
Some(Mushroom {
pos,
stalk: 12.0 + rng.gen::<f32>().powf(2.0) * 35.0,
head_color: Rgb::new(
50,
rng.gen_range(70..110),
rng.gen_range(100..200),
),
})
} else {
None
}
}) {
mushroom
} else {
continue;
};
let wposf = wpos.map(|e| e as f64);
let warp_freq = 1.0 / 32.0;
let warp_amp = Vec3::new(12.0, 12.0, 12.0);
let wposf_warped = wposf.map(|e| e as f32)
+ Vec3::new(
FastNoise::new(seed).get(wposf * warp_freq) as f32,
FastNoise::new(seed + 1).get(wposf * warp_freq) as f32,
FastNoise::new(seed + 2).get(wposf * warp_freq) as f32,
) * warp_amp
* (wposf.z as f32 - mushroom.pos.z as f32)
.mul(0.1)
.clamped(0.0, 1.0);
let rpos = wposf_warped - mushroom.pos.map(|e| e as f32).map(|e| e as f32);
let stalk_radius = 2.5f32;
let head_radius = 18.0f32;
let head_height = 16.0;
let dist_sq = rpos.xy().magnitude_squared();
if dist_sq < head_radius.powi(2) {
let dist = dist_sq.sqrt();
let head_dist = ((rpos - Vec3::unit_z() * mushroom.stalk)
/ Vec2::broadcast(head_radius).with_z(head_height))
.magnitude();
let stalk = mushroom.stalk + Lerp::lerp(head_height * 0.5, 0.0, dist / head_radius);
// Head
if rpos.z > stalk
&& rpos.z <= mushroom.stalk + head_height
&& dist
< head_radius * (1.0 - (rpos.z - mushroom.stalk) / head_height).powf(0.125)
{
if head_dist < 0.85 {
let radial = (rpos.x.atan2(rpos.y) * 10.0).sin() * 0.5 + 0.5;
return Some(Block::new(
BlockKind::GlowingMushroom,
Rgb::new(30, 50 + (radial * 100.0) as u8, 100 - (radial * 50.0) as u8),
));
} else if head_dist < 1.0 {
return Some(Block::new(BlockKind::Wood, mushroom.head_color));
}
}
if rpos.z <= mushroom.stalk + head_height - 1.0
&& dist_sq
< (stalk_radius * Lerp::lerp(1.5, 0.75, rpos.z / mushroom.stalk)).powi(2)
{
// Stalk
return Some(Block::new(BlockKind::Wood, Rgb::new(25, 60, 90)));
} else if ((mushroom.stalk - 0.1)..(mushroom.stalk + 0.9)).contains(&rpos.z) // Hanging orbs
&& dist > head_radius * 0.85
&& dynamic_rng.gen_bool(0.1)
{
use SpriteKind::*;
let sprites = if dynamic_rng.gen_bool(0.1) {
&[Beehive, Lantern] as &[_]
} else {
&[Orb, CavernMycelBlue, CavernMycelBlue] as &[_]
};
return Some(Block::air(*sprites.choose(dynamic_rng).unwrap()));
}
}
}
None
};
canvas.foreach_col(|canvas, wpos2d, _col| {
if canvern_nz_at(wpos2d) <= 0.0 {
return;
}
let (
cavern_bottom,
cavern_top,
cavern_avg_bottom,
cavern_avg_top,
floor,
stalactite,
water_level,
) = cavern_at(wpos2d);
let mini_stalactite = info
.index()
.noise
.cave_nz
.get(wpos2d.map(|e| e as f64 * 0.08).into_array())
.sub(0.5)
.max(0.0)
.mul(
((cavern_top - cavern_bottom) as f64 - 5.0)
.mul(0.15)
.clamped(0.0, 1.0),
)
.mul(24.0 + (cavern_avg_top - cavern_avg_bottom) as f64 * 0.2);
let stalactite_height = (stalactite + mini_stalactite) as i32;
let moss_common = 1.5;
let moss = info
.index()
.noise
.cave_nz
.get(wpos2d.map(|e| e as f64 * 0.035).into_array())
.sub(1.0 - moss_common)
.max(0.0)
.mul(1.0 / moss_common)
.powf(8.0 * moss_common)
.mul(
((cavern_top - cavern_bottom) as f64)
.mul(0.15)
.clamped(0.0, 1.0),
)
.mul(16.0 + (cavern_avg_top - cavern_avg_bottom) as f64 * 0.35);
let plant_factor = info
.index()
.noise
.cave_nz
.get(wpos2d.map(|e| e as f64 * 0.015).into_array())
.add(1.0)
.mul(0.5)
.powf(2.0);
let is_vine = |wpos: Vec3<f32>, dynamic_rng: &mut R| {
let wpos = wpos + wpos.xy().yx().with_z(0.0) * 0.2; // A little twist
let dims = Vec2::new(7.0, 256.0); // Long and thin
let vine_posf = (wpos + Vec2::new(0.0, (wpos.x / dims.x).floor() * 733.0)) / dims; // ~Random offset
let vine_pos = vine_posf.map(|e| e.floor() as i32);
let mut rng = RandomPerm::new(((vine_pos.x << 16) | vine_pos.y) as u32); // Rng for vine attributes
if rng.gen_bool(0.2) {
let vine_height = (cavern_avg_top - cavern_avg_bottom).max(64) as f32;
let vine_base = cavern_avg_bottom as f32 + rng.gen_range(48.0..vine_height);
let vine_y = (vine_posf.y.fract() - 0.5).abs() * 2.0 * dims.y;
let vine_reach = (vine_y * 0.05).powf(2.0).min(1024.0);
let vine_z = vine_base + vine_reach;
if Vec2::new(vine_posf.x.fract() * 2.0 - 1.0, (wpos.z - vine_z) / 5.0)
.magnitude_squared()
< 1.0f32
{
let kind = if dynamic_rng.gen_bool(0.025) {
BlockKind::GlowingRock
} else {
BlockKind::Leaves
};
Some(Block::new(
kind,
Rgb::new(
85,
(vine_y + vine_reach).mul(0.05).sin().mul(35.0).add(85.0) as u8,
20,
),
))
} else {
None
}
} else {
None
}
};
let cavern_top = cavern_top as i32;
let mut last_kind = BlockKind::Rock;
for z in cavern_bottom - 1..cavern_top {
use SpriteKind::*;
let wpos = wpos2d.with_z(z);
let wposf = wpos.map(|e| e as f32);
let block = if z < cavern_bottom {
if z > water_level + dynamic_rng.gen_range(4..16) {
Block::new(BlockKind::Grass, Rgb::new(10, 75, 90))
} else {
Block::new(BlockKind::Rock, Rgb::new(50, 40, 10))
}
} else if z < cavern_bottom + floor {
Block::new(BlockKind::WeakRock, Rgb::new(110, 120, 150))
} else if z > cavern_top - stalactite_height {
if dynamic_rng.gen_bool(0.0035) {
// Glowing rock in stalactites
Block::new(BlockKind::GlowingRock, Rgb::new(30, 150, 120))
} else {
Block::new(BlockKind::WeakRock, Rgb::new(110, 120, 150))
}
} else if let Some(mushroom_block) = get_mushroom(wpos, dynamic_rng) {
mushroom_block
} else if z > cavern_top - moss as i32 {
let kind = if dynamic_rng
.gen_bool(0.05 / (1.0 + ((cavern_top - z).max(0) as f64).mul(0.1)))
{
BlockKind::GlowingMushroom
} else {
BlockKind::Leaves
};
Block::new(kind, Rgb::new(50, 120, 160))
} else if z < water_level {
Block::water(SpriteKind::Empty).with_sprite(
if z == cavern_bottom + floor && dynamic_rng.gen_bool(0.01) {
*[
SpriteKind::Seagrass,
SpriteKind::SeaGrapes,
SpriteKind::SeaweedTemperate,
SpriteKind::StonyCoral,
]
.choose(dynamic_rng)
.unwrap()
} else {
SpriteKind::Empty
},
)
} else if z == water_level
&& dynamic_rng.gen_bool(Lerp::lerp(0.0, 0.05, plant_factor))
&& last_kind == BlockKind::Water
{
Block::air(SpriteKind::CavernLillypadBlue)
} else if z == cavern_bottom + floor
&& dynamic_rng.gen_bool(Lerp::lerp(0.0, 0.5, plant_factor))
&& last_kind == BlockKind::Grass
{
Block::air(
*if dynamic_rng.gen_bool(0.9) {
// High density
&[
CavernGrassBlueShort,
CavernGrassBlueMedium,
CavernGrassBlueLong,
] as &[_]
} else if dynamic_rng.gen_bool(0.5) {
// Medium density
&[CaveMushroom] as &[_]
} else {
// Low density
&[LeafyPlant, Fern, Pyrebloom, Moonbell, Welwitch, GrassBlue] as &[_]
}
.choose(dynamic_rng)
.unwrap(),
)
} else if z == cavern_top - 1 && dynamic_rng.gen_bool(0.001) {
Block::air(
*[CrystalHigh, CeilingMushroom, Orb, CavernMycelBlue]
.choose(dynamic_rng)
.unwrap(),
)
} else if let Some(vine) = is_vine(wposf, dynamic_rng)
.or_else(|| is_vine(wposf.xy().yx().with_z(wposf.z), dynamic_rng))
{
vine
} else {
Block::empty()
};
last_kind = block.kind();
let block = if block.is_filled() {
Block::new(
block.kind(),
block.get_color().unwrap_or_default().map(|e| {
(e as f32 * dynamic_rng.gen_range(0.95..1.05)).clamped(0.0, 255.0) as u8
}),
)
} else {
block
};
let _ = canvas.set(wpos, block);
}
});
}

View File

@ -28,7 +28,7 @@ pub mod util;
// Reexports
pub use crate::{
canvas::{Canvas, CanvasInfo},
config::CONFIG,
config::{Features, CONFIG},
land::Land,
};
pub use block::BlockGen;
@ -52,7 +52,8 @@ use common::{
vol::{ReadVol, RectVolSize, WriteVol},
};
use common_net::msg::{world_msg, WorldMapMsg};
use rand::Rng;
use rand::{prelude::*, Rng};
use rand_chacha::ChaCha8Rng;
use serde::Deserialize;
use std::time::Duration;
use vek::*;
@ -329,7 +330,7 @@ impl World {
};
// Only use for rng affecting dynamic elements like chests and entities!
let mut dynamic_rng = rand::thread_rng();
let mut dynamic_rng = ChaCha8Rng::from_seed(thread_rng().gen());
// Apply layers (paths, caves, etc.)
let mut canvas = Canvas {
@ -346,12 +347,27 @@ impl World {
entities: Vec::new(),
};
layer::apply_caves_to(&mut canvas, &mut dynamic_rng);
layer::apply_shrubs_to(&mut canvas, &mut dynamic_rng);
layer::apply_trees_to(&mut canvas, &mut dynamic_rng);
layer::apply_scatter_to(&mut canvas, &mut dynamic_rng);
layer::apply_paths_to(&mut canvas);
layer::apply_spots_to(&mut canvas, &mut dynamic_rng);
if index.features.caverns {
layer::apply_caverns_to(&mut canvas, &mut dynamic_rng);
}
if index.features.caves {
layer::apply_caves_to(&mut canvas, &mut dynamic_rng);
}
if index.features.shrubs {
layer::apply_shrubs_to(&mut canvas, &mut dynamic_rng);
}
if index.features.trees {
layer::apply_trees_to(&mut canvas, &mut dynamic_rng);
}
if index.features.scatter {
layer::apply_scatter_to(&mut canvas, &mut dynamic_rng);
}
if index.features.paths {
layer::apply_paths_to(&mut canvas);
}
if index.features.spots {
layer::apply_spots_to(&mut canvas, &mut dynamic_rng);
}
// layer::apply_coral_to(&mut canvas);
// Apply site generation
@ -364,7 +380,7 @@ impl World {
entities: canvas.entities,
};
let gen_entity_pos = |dynamic_rng: &mut rand::rngs::ThreadRng| {
let gen_entity_pos = |dynamic_rng: &mut ChaCha8Rng| {
let lpos2d = TerrainChunkSize::RECT_SIZE
.map(|sz| dynamic_rng.gen::<u32>().rem_euclid(sz) as i32);
let mut lpos = Vec3::new(