diff --git a/Cargo.lock b/Cargo.lock index 2dc5d8324d..e45c0a8754 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6275,6 +6275,7 @@ dependencies = [ "ordered-float 2.8.0", "profiling", "rand 0.8.4", + "rand_chacha 0.3.1", "rayon", "rodio", "ron", diff --git a/assets/voxygen/shaders/fluid-frag/cheap.glsl b/assets/voxygen/shaders/fluid-frag/cheap.glsl index f405fdd039..6247522baa 100644 --- a/assets/voxygen/shaders/fluid-frag/cheap.glsl +++ b/assets/voxygen/shaders/fluid-frag/cheap.glsl @@ -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; diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl index 67aae23766..aee8c55051 100644 --- a/assets/voxygen/shaders/fluid-frag/shiny.glsl +++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl @@ -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); diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl index 85fee047c6..889147e9c2 100644 --- a/assets/voxygen/shaders/include/cloud/regular.glsl +++ b/assets/voxygen/shaders/include/cloud/regular.glsl @@ -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; } diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl index cd209c2a4f..d5dafb6253 100644 --- a/assets/voxygen/shaders/include/sky.glsl +++ b/assets/voxygen/shaders/include/sky.glsl @@ -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 } diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index aa5b0e89a2..474f9ddca4 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -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); diff --git a/assets/voxygen/voxel/sprite/cavern/grass_long-0.vox b/assets/voxygen/voxel/sprite/cavern/grass_long-0.vox new file mode 100644 index 0000000000..60167ffa74 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_long-0.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_long-1.vox b/assets/voxygen/voxel/sprite/cavern/grass_long-1.vox new file mode 100644 index 0000000000..0408f49409 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_long-1.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_long-2.vox b/assets/voxygen/voxel/sprite/cavern/grass_long-2.vox new file mode 100644 index 0000000000..68d7e933ad Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_long-2.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_long-3.vox b/assets/voxygen/voxel/sprite/cavern/grass_long-3.vox new file mode 100644 index 0000000000..77486f2852 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_long-3.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_long-4.vox b/assets/voxygen/voxel/sprite/cavern/grass_long-4.vox new file mode 100644 index 0000000000..449dc5c932 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_long-4.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_long-5.vox b/assets/voxygen/voxel/sprite/cavern/grass_long-5.vox new file mode 100644 index 0000000000..1ca52e1371 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_long-5.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_long-6.vox b/assets/voxygen/voxel/sprite/cavern/grass_long-6.vox new file mode 100644 index 0000000000..2dbd780408 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_long-6.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_long-7.vox b/assets/voxygen/voxel/sprite/cavern/grass_long-7.vox new file mode 100644 index 0000000000..00e68b5a2e Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_long-7.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_med-0.vox b/assets/voxygen/voxel/sprite/cavern/grass_med-0.vox new file mode 100644 index 0000000000..efd92a2f77 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_med-0.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_med-1.vox b/assets/voxygen/voxel/sprite/cavern/grass_med-1.vox new file mode 100644 index 0000000000..130f4f2354 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_med-1.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_med-2.vox b/assets/voxygen/voxel/sprite/cavern/grass_med-2.vox new file mode 100644 index 0000000000..471659b4c9 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_med-2.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_med-3.vox b/assets/voxygen/voxel/sprite/cavern/grass_med-3.vox new file mode 100644 index 0000000000..0c3b1672e1 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_med-3.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_short-0.vox b/assets/voxygen/voxel/sprite/cavern/grass_short-0.vox new file mode 100644 index 0000000000..4db9257128 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_short-0.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_short-1.vox b/assets/voxygen/voxel/sprite/cavern/grass_short-1.vox new file mode 100644 index 0000000000..9599de18ac Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_short-1.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_short-2.vox b/assets/voxygen/voxel/sprite/cavern/grass_short-2.vox new file mode 100644 index 0000000000..25c2961619 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_short-2.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_short-3.vox b/assets/voxygen/voxel/sprite/cavern/grass_short-3.vox new file mode 100644 index 0000000000..69817b75e2 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_short-3.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/grass_short-4.vox b/assets/voxygen/voxel/sprite/cavern/grass_short-4.vox new file mode 100644 index 0000000000..c53327ec9e Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/grass_short-4.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/lillypad-0.vox b/assets/voxygen/voxel/sprite/cavern/lillypad-0.vox new file mode 100644 index 0000000000..cfbe8bfdda Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/lillypad-0.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/lillypad-1.vox b/assets/voxygen/voxel/sprite/cavern/lillypad-1.vox new file mode 100644 index 0000000000..41d654a8cd Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/lillypad-1.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/lillypad-2.vox b/assets/voxygen/voxel/sprite/cavern/lillypad-2.vox new file mode 100644 index 0000000000..55bd1a25b0 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/lillypad-2.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/lillypad-3.vox b/assets/voxygen/voxel/sprite/cavern/lillypad-3.vox new file mode 100644 index 0000000000..66cc3d1f23 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/lillypad-3.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/lillypad-4.vox b/assets/voxygen/voxel/sprite/cavern/lillypad-4.vox new file mode 100644 index 0000000000..4734a99dd0 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/lillypad-4.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/mycel-0.vox b/assets/voxygen/voxel/sprite/cavern/mycel-0.vox new file mode 100644 index 0000000000..0798cef6ae Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/mycel-0.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/mycel-1.vox b/assets/voxygen/voxel/sprite/cavern/mycel-1.vox new file mode 100644 index 0000000000..1bcb018930 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/mycel-1.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/mycel-2.vox b/assets/voxygen/voxel/sprite/cavern/mycel-2.vox new file mode 100644 index 0000000000..1c00fde4b2 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/mycel-2.vox differ diff --git a/assets/voxygen/voxel/sprite/cavern/mycel-3.vox b/assets/voxygen/voxel/sprite/cavern/mycel-3.vox new file mode 100644 index 0000000000..51a4a7bfb7 Binary files /dev/null and b/assets/voxygen/voxel/sprite/cavern/mycel-3.vox differ diff --git a/assets/voxygen/voxel/sprite_manifest.ron b/assets/voxygen/voxel/sprite_manifest.ron index 79f99e2112..7672a1b9a5 100644 --- a/assets/voxygen/voxel/sprite_manifest.ron +++ b/assets/voxygen/voxel/sprite_manifest.ron @@ -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, +)), ) diff --git a/assets/world/features.ron b/assets/world/features.ron new file mode 100644 index 0000000000..05c703d615 --- /dev/null +++ b/assets/world/features.ron @@ -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, +) diff --git a/assets/world/style/colors.ron b/assets/world/style/colors.ron index c01b322e19..ce0f153bcc 100644 --- a/assets/world/style/colors.ron +++ b/assets/world/style/colors.ron @@ -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), diff --git a/common/net/src/msg/compression.rs b/common/net/src/msg/compression.rs index db2bd0c31d..fc7f9aaea0 100644 --- a/common/net/src/msg/compression.rs +++ b/common/net/src/msg/compression.rs @@ -644,6 +644,11 @@ impl VoxelImageDecoding for TriPngEncoding Rgb { + r: 50, + g: 250, + b: 250, + }, Misc => Rgb { r: 255, g: 0, diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index d6420ec348..8665c20ed2 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -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 { 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 diff --git a/common/src/terrain/chonk.rs b/common/src/terrain/chonk.rs index 359479e7de..277d0380c3 100644 --- a/common/src/terrain/chonk.rs +++ b/common/src/terrain/chonk.rs @@ -71,6 +71,22 @@ impl Chonk { 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, &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::::SIZE.z as i32; + sc.vol_iter(Vec3::zero(), SubChunkSize::::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] diff --git a/common/src/terrain/sprite.rs b/common/src/terrain/sprite.rs index 05f046f092..f4b4269de4 100644 --- a/common/src/terrain/sprite.rs +++ b/common/src/terrain/sprite.rs @@ -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, }) } diff --git a/server/src/lib.rs b/server/src/lib.rs index be13805968..fb69658408 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -851,7 +851,7 @@ impl Server { let ecs = self.state.ecs_mut(); let slow_jobs = ecs.write_resource::(); - index.reload_colors_if_changed(|index| { + index.reload_if_changed(|index| { let mut chunk_generator = ecs.write_resource::(); let client = ecs.read_storage::(); let mut terrain = ecs.write_resource::(); diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index f34934e846..789ab89ecd 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -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} diff --git a/voxygen/src/scene/terrain/watcher.rs b/voxygen/src/scene/terrain/watcher.rs index 8becc3e3e4..bcf3a952ff 100644 --- a/voxygen/src/scene/terrain/watcher.rs +++ b/voxygen/src/scene/terrain/watcher.rs @@ -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, diff --git a/world/examples/settlement_viewer.rs b/world/examples/settlement_viewer.rs index d2c311827b..d397a2dd06 100644 --- a/world/examples/settlement_viewer.rs +++ b/world/examples/settlement_viewer.rs @@ -17,7 +17,12 @@ fn main() { let mut focus = Vec2::::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]; diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 4fa873f159..2d77fb8cf8 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -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 diff --git a/world/src/config.rs b/world/src/config.rs index e6db31e6f2..b666c82c24 100644 --- a/world/src/config.rs +++ b/world/src/config.rs @@ -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"; +} diff --git a/world/src/index.rs b/world/src/index.rs index 7d5d6e4685..1e69f6f310 100644 --- a/world/src/index.rs +++ b/world/src/index.rs @@ -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, DensityFn)>, colors: AssetHandle>, + features: AssetHandle>, } /// An owned reference to indexed data. @@ -32,6 +34,7 @@ pub struct Index { #[derive(Clone)] pub struct IndexOwned { colors: Arc, + features: Arc, index: Arc, } @@ -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::::load_expect(WORLD_COLORS_MANIFEST); + let features = Arc::::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> { self.colors } + pub fn features(&self) -> AssetHandle> { self.features } + pub fn get_site_prices(&self, site_id: SiteId) -> Option { 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( - &mut self, - reload: impl FnOnce(&mut Self) -> R, - ) -> Option { - self.index.colors.reloaded_global().then(move || { - // Reload the color from the asset handle, which is updated automatically + pub fn reload_if_changed(&mut self, reload: impl FnOnce(&mut Self) -> R) -> Option { + 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, } } diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 9d3148eb80..7bf1aac864 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -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(canvas: &mut Canvas, dynamic_rng: &mut R) { + let info = canvas.info(); + + let canvern_nz_at = |wpos2d: Vec2| { + // 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, + stalk: f32, + head_color: Rgb, + } + + // Get mushroom block, if any, at a position + let mut get_mushroom = |wpos: Vec3, 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::().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, 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); + } + }); +} diff --git a/world/src/lib.rs b/world/src/lib.rs index 72edb90318..182c2a94d4 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -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::().rem_euclid(sz) as i32); let mut lpos = Vec3::new(