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 3357008601
48 changed files with 953 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);

BIN
assets/voxygen/voxel/sprite/cavern/grass_long-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_long-1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_long-2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_long-3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_long-4.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_long-5.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_long-6.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_long-7.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_med-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_med-1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_med-2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_med-3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_short-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_short-1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_short-2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_short-3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/grass_short-4.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/lillypad-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/lillypad-1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/lillypad-2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/lillypad-3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/lillypad-4.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/mycel-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/mycel-1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/mycel-2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/cavern/mycel-3.vox (Stored with Git LFS) Normal file

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(