mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/caves2' into 'master'
Cave Overhaul See merge request veloren/veloren!3454
This commit is contained in:
commit
d41e13c7b0
@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Updated Brazilian Portuguese Translation
|
||||
- Lightning storms
|
||||
- More varied ambient birdcalls
|
||||
- Cave biomes
|
||||
|
||||
### Changed
|
||||
|
||||
@ -58,6 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Optimized sprite processing decreasing the startup time of voxygen (and long freezes when trying
|
||||
to enter the world when this hasn't finished).
|
||||
- Metadata added to music files. Listen to the soundtrack more easily!
|
||||
- Overhauled caves: they're now a multi-layer network spanning the entire world
|
||||
|
||||
### Removed
|
||||
- Removed the options for single and cumulated damage.
|
||||
|
@ -92,9 +92,9 @@ void main() {
|
||||
// vec3 f_col = f_col_light.rgb;
|
||||
// float f_ao = f_col_light.a;
|
||||
|
||||
float f_ao, f_glow;
|
||||
float f_ao, f_glow, f_ao_unused;
|
||||
uint material = 0xFFu;
|
||||
vec3 f_col = greedy_extract_col_light_attr(t_col_light, s_col_light, f_uv_pos, f_ao, f_glow, material);
|
||||
vec3 f_col = greedy_extract_col_light_attr(t_col_light, s_col_light, f_uv_pos, f_ao, f_glow, f_ao_unused, material);
|
||||
|
||||
#ifdef EXPERIMENTAL_BAREMINIMUM
|
||||
tgt_color = vec4(simple_lighting(f_pos.xyz, f_col, f_ao), 1);
|
||||
@ -212,10 +212,16 @@ void main() {
|
||||
: compute_attenuation_point(f_pos, -view_dir, mu, fluid_alt, /*cam_pos.z <= fluid_alt ? cam_pos.xyz : f_pos*/cam_pos.xyz);
|
||||
#endif
|
||||
|
||||
// Prevent the sky affecting light when underground
|
||||
float not_underground = clamp((f_pos.z - f_alt) / 128.0 + 1.0, 0.0, 1.0);
|
||||
|
||||
max_light += get_sun_diffuse2(sun_info, moon_info, f_norm, view_dir, f_pos, mu, cam_attenuation, fluid_alt, k_a, k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light);
|
||||
|
||||
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);
|
||||
|
||||
// TODO: Hack to add a small amount of underground ambient light to the scene
|
||||
reflected_light += vec3(0.01, 0.02, 0.03) * (1.0 - not_underground);
|
||||
|
||||
float ao = f_ao * sqrt(f_ao);//0.25 + f_ao * 0.75; ///*pow(f_ao, 0.5)*/f_ao * 0.85 + 0.15;
|
||||
|
||||
// For now, just make glowing material light be the same colour as the surface
|
||||
|
@ -281,6 +281,8 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
|
||||
sun_color * sun_scatter * get_sun_brightness() * (sun_access * (1.0 - cloud_darken) * cloud_diffuse /*+ sky_color * global_scatter_factor*/) +
|
||||
moon_color * moon_scatter * get_moon_brightness() * (moon_access * (1.0 - cloud_darken) * cloud_diffuse /*+ sky_color * global_scatter_factor*/) +
|
||||
sky_light * (1.0 - global_darken) * not_underground +
|
||||
// A small amount fake ambient light underground
|
||||
(1.0 - not_underground) * vec3(0.2, 0.35, 0.5) * (1.0 - global_darken) +
|
||||
emission * density_integrals.y * step;
|
||||
|
||||
// Rainbow
|
||||
|
@ -153,7 +153,7 @@ float lights_at(vec3 wpos, vec3 wnorm, vec3 /*cam_to_frag*/view_dir, vec3 mu, ve
|
||||
|
||||
// float strength = attenuation_strength(difference);// pow(attenuation_strength(difference), 0.6);
|
||||
// NOTE: This normalizes strength to 0.25 at the center of the point source.
|
||||
float strength = 1.0 / (4 + distance_2);
|
||||
float strength = 3.0 / (5 + distance_2);
|
||||
|
||||
// Multiply the vec3 only once
|
||||
const float PI = 3.1415926535897932384626433832795;
|
||||
|
@ -620,35 +620,38 @@ vec3 compute_attenuation_point(vec3 wpos, vec3 ray_dir, vec3 mu, float surface_a
|
||||
//}
|
||||
//#endif
|
||||
|
||||
vec3 greedy_extract_col_light_attr(texture2D t_col_light, sampler s_col_light, vec2 f_uv_pos, out float f_light, out float f_glow, out uint f_attr) {
|
||||
uvec4 f_col_light = uvec4(texelFetch(sampler2D(t_col_light, s_col_light), ivec2(f_uv_pos), 0) * 255);
|
||||
vec3 f_col = vec3(
|
||||
float(((f_col_light.r & 0x7u) << 1u) | (f_col_light.b & 0xF0u)),
|
||||
float(f_col_light.a),
|
||||
float(((f_col_light.g & 0x7u) << 1u) | ((f_col_light.b & 0x0Fu) << 4u))
|
||||
) / 255.0;
|
||||
|
||||
vec3 greedy_extract_col_light_attr(texture2D t_col_light, sampler s_col_light, vec2 f_uv_pos, out float f_light, out float f_glow, out float f_ao, out uint f_attr) {
|
||||
// TODO: Figure out how to use `texture` and modulation to avoid needing to do manual filtering
|
||||
vec2 light_00 = vec2(uvec2(f_col_light.rg) >> 3u);
|
||||
vec2 light_10 = vec2(uvec2(texelFetch(sampler2D(t_col_light, s_col_light), ivec2(f_uv_pos) + ivec2(1, 0), 0).rg * 255.0) >> 3u);
|
||||
vec2 light_01 = vec2(uvec2(texelFetch(sampler2D(t_col_light, s_col_light), ivec2(f_uv_pos) + ivec2(0, 1), 0).rg * 255.0) >> 3u);
|
||||
vec2 light_11 = vec2(uvec2(texelFetch(sampler2D(t_col_light, s_col_light), ivec2(f_uv_pos) + ivec2(1, 1), 0).rg * 255.0) >> 3u);
|
||||
vec2 light_0 = mix(light_00, light_01, fract(f_uv_pos.y));
|
||||
vec2 light_1 = mix(light_10, light_11, fract(f_uv_pos.y));
|
||||
vec2 light = mix(light_0, light_1, fract(f_uv_pos.x));
|
||||
uvec4 tex_00 = uvec4(texelFetch(sampler2D(t_col_light, s_col_light), ivec2(f_uv_pos) + ivec2(0, 0), 0) * 255);
|
||||
uvec4 tex_10 = uvec4(texelFetch(sampler2D(t_col_light, s_col_light), ivec2(f_uv_pos) + ivec2(1, 0), 0) * 255);
|
||||
uvec4 tex_01 = uvec4(texelFetch(sampler2D(t_col_light, s_col_light), ivec2(f_uv_pos) + ivec2(0, 1), 0) * 255);
|
||||
uvec4 tex_11 = uvec4(texelFetch(sampler2D(t_col_light, s_col_light), ivec2(f_uv_pos) + ivec2(1, 1), 0) * 255);
|
||||
vec3 light_00 = vec3(tex_00.rg >> 3u, tex_00.a & 1u);
|
||||
vec3 light_10 = vec3(tex_10.rg >> 3u, tex_10.a & 1u);
|
||||
vec3 light_01 = vec3(tex_01.rg >> 3u, tex_01.a & 1u);
|
||||
vec3 light_11 = vec3(tex_11.rg >> 3u, tex_11.a & 1u);
|
||||
vec3 light_0 = mix(light_00, light_01, fract(f_uv_pos.y));
|
||||
vec3 light_1 = mix(light_10, light_11, fract(f_uv_pos.y));
|
||||
vec3 light = mix(light_0, light_1, fract(f_uv_pos.x));
|
||||
// TODO: Use `texture` instead
|
||||
//vec2 light = texture(t_col_light, f_uv_pos).xy / 31;
|
||||
|
||||
vec3 f_col = vec3(
|
||||
float(((tex_00.r & 0x7u) << 1u) | (tex_00.b & 0xF0u)),
|
||||
float(tex_00.a & 0xFE),
|
||||
float(((tex_00.g & 0x7u) << 1u) | ((tex_00.b & 0x0Fu) << 4u))
|
||||
) / 255.0;
|
||||
|
||||
f_ao = light.z;
|
||||
f_light = light.x / 31.0;
|
||||
f_glow = light.y / 31.0;
|
||||
f_attr = f_col_light.g >> 3u;
|
||||
f_attr = tex_00.g >> 3u;
|
||||
return srgb_to_linear(f_col);
|
||||
}
|
||||
|
||||
vec3 greedy_extract_col_light_glow(texture2D t_col_light, sampler s_col_light, vec2 f_uv_pos, out float f_light, out float f_glow) {
|
||||
vec3 greedy_extract_col_light_glow(texture2D t_col_light, sampler s_col_light, vec2 f_uv_pos, out float f_light, out float f_ao, out float f_glow) {
|
||||
uint f_attr;
|
||||
return greedy_extract_col_light_attr(t_col_light, s_col_light, f_uv_pos, f_light, f_glow, f_attr);
|
||||
return greedy_extract_col_light_attr(t_col_light, s_col_light, f_uv_pos, f_light, f_glow, f_ao, f_attr);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -40,8 +40,8 @@ layout(location = 0) out vec4 tgt_color;
|
||||
const float FADE_DIST = 32.0;
|
||||
|
||||
void main() {
|
||||
float f_ao, f_glow;
|
||||
vec3 f_col = greedy_extract_col_light_glow(t_col_light, s_col_light, f_uv_pos, f_ao, f_glow);
|
||||
float f_ao, f_glow, f_ao_unused;
|
||||
vec3 f_col = greedy_extract_col_light_glow(t_col_light, s_col_light, f_uv_pos, f_ao, f_ao_unused, f_glow);
|
||||
|
||||
#ifdef EXPERIMENTAL_BAREMINIMUM
|
||||
tgt_color = vec4(simple_lighting(f_pos.xyz, f_col, f_ao), 1);
|
||||
@ -100,6 +100,9 @@ void main() {
|
||||
: compute_attenuation_point(f_pos, -view_dir, mu, fluid_alt, /*cam_pos.z <= fluid_alt ? cam_pos.xyz : f_pos*/cam_pos.xyz);
|
||||
#endif
|
||||
|
||||
// Prevent the sky affecting light when underground
|
||||
float not_underground = clamp((f_pos.z - f_alt) / 128.0 + 1.0, 0.0, 1.0);
|
||||
|
||||
max_light += get_sun_diffuse2(sun_info, moon_info, f_norm, view_dir, f_pos, mu, cam_attenuation, fluid_alt, k_a, k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light);
|
||||
|
||||
emitted_light *= sun_info.block;
|
||||
@ -107,6 +110,9 @@ void main() {
|
||||
|
||||
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);
|
||||
|
||||
// TODO: Hack to add a small amount of underground ambient light to the scene
|
||||
reflected_light += vec3(0.01, 0.02, 0.03) * (1.0 - not_underground);
|
||||
|
||||
vec3 glow = pow(f_inst_light.y, 3) * 4 * glow_light(f_pos);
|
||||
emitted_light += glow * cam_attenuation;
|
||||
|
||||
|
@ -83,8 +83,10 @@ void main() {
|
||||
f_pos += chunk_offs;
|
||||
|
||||
#ifndef EXPERIMENTAL_BAREMINIMUM
|
||||
// Terrain 'pop-in' effect
|
||||
f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0));
|
||||
#ifndef EXPERIMENTAL_NOTERRAINPOP
|
||||
// Terrain 'pop-in' effect
|
||||
f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef EXPERIMENTAL_CURVEDWORLD
|
||||
|
@ -84,8 +84,8 @@ void main() {
|
||||
vec2 f_uv_pos = f_uv_pos + atlas_offs.xy;
|
||||
// vec4 f_col_light = textureProj(t_col_light, vec3(f_uv_pos + 0.5, textureSize(t_col_light, 0)));//(f_uv_pos/* + 0.5*/) / texSize);
|
||||
// float f_light = textureProj(t_col_light, vec3(f_uv_pos + 0.5, textureSize(t_col_light, 0))).a;//1.0;//f_col_light.a * 4.0;// f_light = float(v_col_light & 0x3Fu) / 64.0;
|
||||
float f_light, f_glow;
|
||||
vec3 f_col = greedy_extract_col_light_glow(t_col_light, s_col_light, f_uv_pos, f_light, f_glow);
|
||||
float f_light, f_glow, f_ao;
|
||||
vec3 f_col = greedy_extract_col_light_glow(t_col_light, s_col_light, f_uv_pos, f_light, f_ao, f_glow);
|
||||
|
||||
#ifdef EXPERIMENTAL_BAREMINIMUM
|
||||
tgt_color = vec4(simple_lighting(f_pos.xyz, f_col, f_light), 1);
|
||||
@ -382,12 +382,17 @@ void main() {
|
||||
reflected_light *= f_light;
|
||||
max_light *= f_light;
|
||||
|
||||
// TODO: Hack to add a small amount of underground ambient light to the scene
|
||||
reflected_light += vec3(0.01, 0.02, 0.03) * (1.0 - not_underground);
|
||||
|
||||
// TODO: Apply AO after this
|
||||
vec3 glow = glow_light(f_pos) * (pow(f_glow, 3) * 5 + pow(f_glow, 2.0) * 2) * pow(max(dot(face_norm, f_norm), 0), 2);
|
||||
reflected_light += glow * cam_attenuation;
|
||||
|
||||
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);
|
||||
|
||||
reflected_light *= 0.4 + f_ao * 0.6;
|
||||
|
||||
#ifndef EXPERIMENTAL_NOCAUSTICS
|
||||
#if (FLUID_MODE == FLUID_MODE_SHINY)
|
||||
if (faces_fluid) {
|
||||
|
@ -9,7 +9,7 @@ type TodoRect = (
|
||||
Vec3<i32>,
|
||||
);
|
||||
|
||||
pub struct GreedyConfig<D, FL, FG, FO, FS, FP, FT> {
|
||||
pub struct GreedyConfig<D, FA, FL, FG, FO, FS, FP, FT> {
|
||||
pub data: D,
|
||||
/// The minimum position to mesh, in the coordinate system used
|
||||
/// for queries against the volume.
|
||||
@ -31,6 +31,9 @@ pub struct GreedyConfig<D, FL, FG, FO, FS, FP, FT> {
|
||||
/// the number of *horizontal* planes large enough to cover the whole
|
||||
/// chunk.
|
||||
pub greedy_size_cross: Vec3<usize>,
|
||||
/// Given a position, return the AO information for the voxel at that
|
||||
/// position (0.0 - 1.0).
|
||||
pub get_ao: FA,
|
||||
/// Given a position, return the lighting information for the voxel at that
|
||||
/// position.
|
||||
pub get_light: FL,
|
||||
@ -367,16 +370,17 @@ impl<'a, Allocator: AtlasAllocator> GreedyMesh<'a, Allocator> {
|
||||
/// Returns an estimate of the bounds of the current meshed model.
|
||||
///
|
||||
/// For more information on the config parameter, see [GreedyConfig].
|
||||
pub fn push<M: PartialEq, D: 'a, FL, FG, FO, FS, FP, FT>(
|
||||
pub fn push<M: PartialEq, D: 'a, FA, FL, FG, FO, FS, FP, FT>(
|
||||
&mut self,
|
||||
config: GreedyConfig<D, FL, FG, FO, FS, FP, FT>,
|
||||
config: GreedyConfig<D, FA, FL, FG, FO, FS, FP, FT>,
|
||||
) where
|
||||
FA: for<'r> FnMut(&'r mut D, Vec3<i32>) -> f32 + 'a,
|
||||
FL: for<'r> FnMut(&'r mut D, Vec3<i32>) -> f32 + 'a,
|
||||
FG: for<'r> FnMut(&'r mut D, Vec3<i32>) -> f32 + 'a,
|
||||
FO: for<'r> FnMut(&'r mut D, Vec3<i32>) -> bool + 'a,
|
||||
FS: for<'r> FnMut(&'r mut D, Vec3<i32>, Vec3<i32>, Vec2<Vec3<i32>>) -> Option<(bool, M)>,
|
||||
FP: FnMut(Vec2<u16>, Vec2<Vec2<u16>>, Vec3<f32>, Vec2<Vec3<f32>>, Vec3<f32>, &M),
|
||||
FT: for<'r> FnMut(&'r mut D, Vec3<i32>, u8, u8) -> [u8; 4] + 'a,
|
||||
FT: for<'r> FnMut(&'r mut D, Vec3<i32>, u8, u8, bool) -> [u8; 4] + 'a,
|
||||
{
|
||||
span!(_guard, "push", "GreedyMesh::push");
|
||||
let cont = greedy_mesh(
|
||||
@ -401,7 +405,7 @@ impl<'a, Allocator: AtlasAllocator> GreedyMesh<'a, Allocator> {
|
||||
span!(_guard, "finalize", "GreedyMesh::finalize");
|
||||
let cur_size = self.col_lights_size;
|
||||
let col_lights = vec![
|
||||
TerrainVertex::make_col_light(254, 0, Rgb::broadcast(254));
|
||||
TerrainVertex::make_col_light(254, 0, Rgb::broadcast(254), true);
|
||||
cur_size.x as usize * cur_size.y as usize
|
||||
];
|
||||
let mut col_lights_info = (col_lights, cur_size);
|
||||
@ -414,7 +418,7 @@ impl<'a, Allocator: AtlasAllocator> GreedyMesh<'a, Allocator> {
|
||||
pub fn max_size(&self) -> Vec2<u16> { self.max_size }
|
||||
}
|
||||
|
||||
fn greedy_mesh<'a, M: PartialEq, D: 'a, FL, FG, FO, FS, FP, FT, Allocator: AtlasAllocator>(
|
||||
fn greedy_mesh<'a, M: PartialEq, D: 'a, FA, FL, FG, FO, FS, FP, FT, Allocator: AtlasAllocator>(
|
||||
atlas: &mut Allocator,
|
||||
col_lights_size: &mut Vec2<u16>,
|
||||
max_size: Vec2<u16>,
|
||||
@ -423,21 +427,23 @@ fn greedy_mesh<'a, M: PartialEq, D: 'a, FL, FG, FO, FS, FP, FT, Allocator: Atlas
|
||||
draw_delta,
|
||||
greedy_size,
|
||||
greedy_size_cross,
|
||||
get_ao,
|
||||
get_light,
|
||||
get_glow,
|
||||
get_opacity,
|
||||
mut should_draw,
|
||||
mut push_quad,
|
||||
make_face_texel,
|
||||
}: GreedyConfig<D, FL, FG, FO, FS, FP, FT>,
|
||||
}: GreedyConfig<D, FA, FL, FG, FO, FS, FP, FT>,
|
||||
) -> Box<SuspendedMesh<'a>>
|
||||
where
|
||||
FA: for<'r> FnMut(&'r mut D, Vec3<i32>) -> f32 + 'a,
|
||||
FL: for<'r> FnMut(&'r mut D, Vec3<i32>) -> f32 + 'a,
|
||||
FG: for<'r> FnMut(&'r mut D, Vec3<i32>) -> f32 + 'a,
|
||||
FO: for<'r> FnMut(&'r mut D, Vec3<i32>) -> bool + 'a,
|
||||
FS: for<'r> FnMut(&'r mut D, Vec3<i32>, Vec3<i32>, Vec2<Vec3<i32>>) -> Option<(bool, M)>,
|
||||
FP: FnMut(Vec2<u16>, Vec2<Vec2<u16>>, Vec3<f32>, Vec2<Vec3<f32>>, Vec3<f32>, &M),
|
||||
FT: for<'r> FnMut(&'r mut D, Vec3<i32>, u8, u8) -> [u8; 4] + 'a,
|
||||
FT: for<'r> FnMut(&'r mut D, Vec3<i32>, u8, u8, bool) -> [u8; 4] + 'a,
|
||||
{
|
||||
span!(_guard, "greedy_mesh");
|
||||
// TODO: Collect information to see if we can choose a good value here.
|
||||
@ -573,6 +579,7 @@ where
|
||||
&mut data,
|
||||
todo_rects,
|
||||
draw_delta,
|
||||
get_ao,
|
||||
get_light,
|
||||
get_glow,
|
||||
get_opacity,
|
||||
@ -725,10 +732,11 @@ fn draw_col_lights<D>(
|
||||
data: &mut D,
|
||||
todo_rects: Vec<TodoRect>,
|
||||
draw_delta: Vec3<i32>,
|
||||
mut get_ao: impl FnMut(&mut D, Vec3<i32>) -> f32,
|
||||
mut get_light: impl FnMut(&mut D, Vec3<i32>) -> f32,
|
||||
mut get_glow: impl FnMut(&mut D, Vec3<i32>) -> f32,
|
||||
mut get_opacity: impl FnMut(&mut D, Vec3<i32>) -> bool,
|
||||
mut make_face_texel: impl FnMut(&mut D, Vec3<i32>, u8, u8) -> [u8; 4],
|
||||
mut make_face_texel: impl FnMut(&mut D, Vec3<i32>, u8, u8, bool) -> [u8; 4],
|
||||
) {
|
||||
todo_rects.into_iter().for_each(|(pos, uv, rect, delta)| {
|
||||
// NOTE: Conversions are safe because width, height, and offset must be
|
||||
@ -793,6 +801,15 @@ fn draw_col_lights<D>(
|
||||
0.0
|
||||
}
|
||||
) / 4.0;
|
||||
let ao = (get_ao(data, light_pos)
|
||||
+ get_ao(data, light_pos - uv.x)
|
||||
+ get_ao(data, light_pos - uv.y)
|
||||
+ if direct_u_opacity || direct_v_opacity {
|
||||
get_ao(data, light_pos - uv.x - uv.y)
|
||||
} else {
|
||||
0.0
|
||||
})
|
||||
/ 4.0;
|
||||
let glowiness = (get_glow(data, light_pos)
|
||||
+ get_glow(data, light_pos - uv.x)
|
||||
+ get_glow(data, light_pos - uv.y)
|
||||
@ -804,7 +821,8 @@ fn draw_col_lights<D>(
|
||||
/ 4.0;
|
||||
let light = (darkness * 31.5) as u8;
|
||||
let glow = (glowiness * 31.5) as u8;
|
||||
*col_light = make_face_texel(data, pos, light, glow);
|
||||
let ao = ao > 0.7;
|
||||
*col_light = make_face_texel(data, pos, light, glow, ao);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -78,6 +78,7 @@ where
|
||||
draw_delta,
|
||||
greedy_size,
|
||||
greedy_size_cross,
|
||||
get_ao: |_: &mut V, _: Vec3<i32>| 1.0,
|
||||
get_light,
|
||||
get_glow,
|
||||
get_opacity,
|
||||
@ -93,7 +94,7 @@ where
|
||||
|atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm),
|
||||
));
|
||||
},
|
||||
make_face_texel: |vol: &mut V, pos, light, _| {
|
||||
make_face_texel: |vol: &mut V, pos, light, _, _| {
|
||||
let cell = vol.get(pos).ok();
|
||||
let (glowy, shiny) = cell
|
||||
.map(|c| (c.is_glowy(), c.is_shiny()))
|
||||
@ -218,6 +219,7 @@ where
|
||||
draw_delta,
|
||||
greedy_size,
|
||||
greedy_size_cross,
|
||||
get_ao: |_: &mut _, _: Vec3<i32>| 1.0,
|
||||
get_light,
|
||||
get_glow,
|
||||
get_opacity,
|
||||
@ -233,8 +235,8 @@ where
|
||||
|atlas_pos, pos, norm, &meta| create_opaque(atlas_pos, pos, norm, meta),
|
||||
));
|
||||
},
|
||||
make_face_texel: move |flat: &mut _, pos, light, glow| {
|
||||
TerrainVertex::make_col_light(light, glow, get_color(flat, pos))
|
||||
make_face_texel: move |flat: &mut _, pos, light, glow, ao| {
|
||||
TerrainVertex::make_col_light(light, glow, get_color(flat, pos), ao)
|
||||
},
|
||||
});
|
||||
|
||||
@ -305,6 +307,7 @@ where
|
||||
draw_delta,
|
||||
greedy_size,
|
||||
greedy_size_cross,
|
||||
get_ao: |_: &mut V, _: Vec3<i32>| 1.0,
|
||||
get_light,
|
||||
get_glow,
|
||||
get_opacity,
|
||||
@ -320,8 +323,8 @@ where
|
||||
|atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm),
|
||||
));
|
||||
},
|
||||
make_face_texel: move |vol: &mut V, pos, light, glow| {
|
||||
TerrainVertex::make_col_light(light, glow, get_color(vol, pos))
|
||||
make_face_texel: move |vol: &mut V, pos, light, glow, ao| {
|
||||
TerrainVertex::make_col_light(light, glow, get_color(vol, pos), ao)
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -375,6 +375,9 @@ pub fn generate_mesh<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + '
|
||||
light(pos + range.min)
|
||||
}
|
||||
};
|
||||
let get_ao = |_: &mut (), pos: Vec3<i32>| {
|
||||
if flat_get(pos).is_opaque() { 0.0 } else { 1.0 }
|
||||
};
|
||||
let get_glow = |_: &mut (), pos: Vec3<i32>| glow(pos + range.min);
|
||||
let get_color =
|
||||
|_: &mut (), pos: Vec3<i32>| flat_get(pos).get_color().unwrap_or_else(Rgb::zero);
|
||||
@ -399,6 +402,7 @@ pub fn generate_mesh<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + '
|
||||
draw_delta,
|
||||
greedy_size,
|
||||
greedy_size_cross,
|
||||
get_ao,
|
||||
get_light,
|
||||
get_glow,
|
||||
get_opacity,
|
||||
@ -427,8 +431,8 @@ pub fn generate_mesh<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + '
|
||||
));
|
||||
},
|
||||
},
|
||||
make_face_texel: |data: &mut (), pos, light, glow| {
|
||||
TerrainVertex::make_col_light(light, glow, get_color(data, pos))
|
||||
make_face_texel: |data: &mut (), pos, light, glow, ao| {
|
||||
TerrainVertex::make_col_light(light, glow, get_color(data, pos), ao)
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -195,7 +195,7 @@ impl RainOcclusionPipeline {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
cull_mode: None,
|
||||
clamp_depth: true,
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
conservative: false,
|
||||
|
@ -67,6 +67,7 @@ impl Vertex {
|
||||
// 0 to 31
|
||||
glow: u8,
|
||||
col: Rgb<u8>,
|
||||
ao: bool,
|
||||
) -> [u8; 4] {
|
||||
//[col.r, col.g, col.b, light]
|
||||
// It would be nice for this to be cleaner, but we want to squeeze 5 fields into
|
||||
@ -92,7 +93,7 @@ impl Vertex {
|
||||
(light.min(31) << 3) | ((col.r >> 1) & 0b111),
|
||||
(glow.min(31) << 3) | ((col.b >> 1) & 0b111),
|
||||
(col.r & 0b11110000) | (col.b >> 4),
|
||||
col.g, // Green is lucky, it remains unscathed
|
||||
(col.g & 0xFE) | ao as u8,
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use super::{terrain::BlocksOfInterest, SceneData, Terrain};
|
||||
use crate::{
|
||||
ecs::comp::Interpolated,
|
||||
mesh::{greedy::GreedyMesh, segment::generate_mesh_base_vol_particle},
|
||||
render::{
|
||||
pipelines::particle::ParticleMode, Instances, Light, Model, ParticleDrawer,
|
||||
@ -347,28 +348,28 @@ impl ParticleMgr {
|
||||
"ParticleMgr::maintain_body_particles"
|
||||
);
|
||||
let ecs = scene_data.state.ecs();
|
||||
for (body, pos, vel) in (
|
||||
for (body, interpolated, vel) in (
|
||||
&ecs.read_storage::<Body>(),
|
||||
&ecs.read_storage::<Pos>(),
|
||||
&ecs.read_storage::<Interpolated>(),
|
||||
ecs.read_storage::<Vel>().maybe(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
match body {
|
||||
Body::Object(object::Body::CampfireLit) => {
|
||||
self.maintain_campfirelit_particles(scene_data, pos, vel)
|
||||
self.maintain_campfirelit_particles(scene_data, interpolated.pos, vel)
|
||||
},
|
||||
Body::Object(object::Body::BoltFire) => {
|
||||
self.maintain_boltfire_particles(scene_data, pos, vel)
|
||||
self.maintain_boltfire_particles(scene_data, interpolated.pos, vel)
|
||||
},
|
||||
Body::Object(object::Body::BoltFireBig) => {
|
||||
self.maintain_boltfirebig_particles(scene_data, pos, vel)
|
||||
self.maintain_boltfirebig_particles(scene_data, interpolated.pos, vel)
|
||||
},
|
||||
Body::Object(object::Body::BoltNature) => {
|
||||
self.maintain_boltnature_particles(scene_data, pos, vel)
|
||||
self.maintain_boltnature_particles(scene_data, interpolated.pos, vel)
|
||||
},
|
||||
Body::Object(object::Body::Tornado) => {
|
||||
self.maintain_tornado_particles(scene_data, pos)
|
||||
self.maintain_tornado_particles(scene_data, interpolated.pos)
|
||||
},
|
||||
Body::Object(
|
||||
object::Body::Bomb
|
||||
@ -378,7 +379,7 @@ impl ParticleMgr {
|
||||
| object::Body::FireworkRed
|
||||
| object::Body::FireworkWhite
|
||||
| object::Body::FireworkYellow,
|
||||
) => self.maintain_bomb_particles(scene_data, pos, vel),
|
||||
) => self.maintain_bomb_particles(scene_data, interpolated.pos, vel),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
@ -387,7 +388,7 @@ impl ParticleMgr {
|
||||
fn maintain_campfirelit_particles(
|
||||
&mut self,
|
||||
scene_data: &SceneData,
|
||||
pos: &Pos,
|
||||
pos: Vec3<f32>,
|
||||
vel: Option<&Vel>,
|
||||
) {
|
||||
span!(
|
||||
@ -404,14 +405,14 @@ impl ParticleMgr {
|
||||
Duration::from_millis(250),
|
||||
time,
|
||||
ParticleMode::CampfireFire,
|
||||
pos.0,
|
||||
pos,
|
||||
));
|
||||
|
||||
self.particles.push(Particle::new(
|
||||
Duration::from_secs(10),
|
||||
time,
|
||||
ParticleMode::CampfireSmoke,
|
||||
pos.0.map(|e| e + thread_rng().gen_range(-0.25..0.25))
|
||||
pos.map(|e| e + thread_rng().gen_range(-0.25..0.25))
|
||||
+ vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::<f32>()),
|
||||
));
|
||||
}
|
||||
@ -420,7 +421,7 @@ impl ParticleMgr {
|
||||
fn maintain_boltfire_particles(
|
||||
&mut self,
|
||||
scene_data: &SceneData,
|
||||
pos: &Pos,
|
||||
pos: Vec3<f32>,
|
||||
vel: Option<&Vel>,
|
||||
) {
|
||||
span!(
|
||||
@ -437,13 +438,13 @@ impl ParticleMgr {
|
||||
Duration::from_millis(500),
|
||||
time,
|
||||
ParticleMode::CampfireFire,
|
||||
pos.0,
|
||||
pos,
|
||||
));
|
||||
self.particles.push(Particle::new(
|
||||
Duration::from_secs(1),
|
||||
time,
|
||||
ParticleMode::CampfireSmoke,
|
||||
pos.0.map(|e| e + rng.gen_range(-0.25..0.25))
|
||||
pos.map(|e| e + rng.gen_range(-0.25..0.25))
|
||||
+ vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::<f32>()),
|
||||
));
|
||||
}
|
||||
@ -452,7 +453,7 @@ impl ParticleMgr {
|
||||
fn maintain_boltfirebig_particles(
|
||||
&mut self,
|
||||
scene_data: &SceneData,
|
||||
pos: &Pos,
|
||||
pos: Vec3<f32>,
|
||||
vel: Option<&Vel>,
|
||||
) {
|
||||
span!(
|
||||
@ -472,7 +473,7 @@ impl ParticleMgr {
|
||||
Duration::from_millis(500),
|
||||
time,
|
||||
ParticleMode::CampfireFire,
|
||||
pos.0.map(|e| e + rng.gen_range(-0.25..0.25))
|
||||
pos.map(|e| e + rng.gen_range(-0.25..0.25))
|
||||
+ vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::<f32>()),
|
||||
)
|
||||
},
|
||||
@ -486,7 +487,7 @@ impl ParticleMgr {
|
||||
Duration::from_secs(2),
|
||||
time,
|
||||
ParticleMode::CampfireSmoke,
|
||||
pos.0.map(|e| e + rng.gen_range(-0.25..0.25))
|
||||
pos.map(|e| e + rng.gen_range(-0.25..0.25))
|
||||
+ vel.map_or(Vec3::zero(), |v| -v.0 * dt),
|
||||
)
|
||||
},
|
||||
@ -496,7 +497,7 @@ impl ParticleMgr {
|
||||
fn maintain_boltnature_particles(
|
||||
&mut self,
|
||||
scene_data: &SceneData,
|
||||
pos: &Pos,
|
||||
pos: Vec3<f32>,
|
||||
vel: Option<&Vel>,
|
||||
) {
|
||||
let time = scene_data.state.get_time();
|
||||
@ -511,14 +512,14 @@ impl ParticleMgr {
|
||||
Duration::from_millis(500),
|
||||
time,
|
||||
ParticleMode::CampfireSmoke,
|
||||
pos.0.map(|e| e + rng.gen_range(-0.25..0.25))
|
||||
pos.map(|e| e + rng.gen_range(-0.25..0.25))
|
||||
+ vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::<f32>()),
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn maintain_tornado_particles(&mut self, scene_data: &SceneData, pos: &Pos) {
|
||||
fn maintain_tornado_particles(&mut self, scene_data: &SceneData, pos: Vec3<f32>) {
|
||||
let time = scene_data.state.get_time();
|
||||
let mut rng = thread_rng();
|
||||
|
||||
@ -530,13 +531,18 @@ impl ParticleMgr {
|
||||
Duration::from_millis(1000),
|
||||
time,
|
||||
ParticleMode::Tornado,
|
||||
pos.0.map(|e| e + rng.gen_range(-0.25..0.25)),
|
||||
pos.map(|e| e + rng.gen_range(-0.25..0.25)),
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn maintain_bomb_particles(&mut self, scene_data: &SceneData, pos: &Pos, vel: Option<&Vel>) {
|
||||
fn maintain_bomb_particles(
|
||||
&mut self,
|
||||
scene_data: &SceneData,
|
||||
pos: Vec3<f32>,
|
||||
vel: Option<&Vel>,
|
||||
) {
|
||||
span!(
|
||||
_guard,
|
||||
"bomb_particles",
|
||||
@ -552,7 +558,7 @@ impl ParticleMgr {
|
||||
Duration::from_millis(1500),
|
||||
time,
|
||||
ParticleMode::GunPowderSpark,
|
||||
pos.0,
|
||||
pos,
|
||||
));
|
||||
|
||||
// smoke
|
||||
@ -560,7 +566,7 @@ impl ParticleMgr {
|
||||
Duration::from_secs(2),
|
||||
time,
|
||||
ParticleMode::CampfireSmoke,
|
||||
pos.0 + vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::<f32>()),
|
||||
pos + vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::<f32>()),
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -577,9 +583,9 @@ impl ParticleMgr {
|
||||
let dt = scene_data.state.get_delta_time();
|
||||
let mut rng = thread_rng();
|
||||
|
||||
for (entity, pos, vel, character_state, body) in (
|
||||
for (entity, interpolated, vel, character_state, body) in (
|
||||
&ecs.entities(),
|
||||
&ecs.read_storage::<Pos>(),
|
||||
&ecs.read_storage::<Interpolated>(),
|
||||
ecs.read_storage::<Vel>().maybe(),
|
||||
&ecs.read_storage::<CharacterState>(),
|
||||
&ecs.read_storage::<Body>(),
|
||||
@ -596,7 +602,8 @@ impl ParticleMgr {
|
||||
Duration::from_secs(15),
|
||||
time,
|
||||
ParticleMode::CampfireSmoke,
|
||||
pos.0 + vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::<f32>()),
|
||||
interpolated.pos
|
||||
+ vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::<f32>()),
|
||||
)
|
||||
},
|
||||
);
|
||||
@ -624,28 +631,30 @@ impl ParticleMgr {
|
||||
)
|
||||
.normalized()
|
||||
* rand_dist
|
||||
+ pos.0
|
||||
+ interpolated.pos
|
||||
+ Vec3::unit_z() * 0.05;
|
||||
Particle::new_directed(
|
||||
Duration::from_millis(900),
|
||||
time,
|
||||
ParticleMode::CultistFlame,
|
||||
init_pos,
|
||||
pos.0,
|
||||
interpolated.pos,
|
||||
)
|
||||
},
|
||||
);
|
||||
// Particles for lifesteal effect
|
||||
for (_entity_b, pos_b, body_b, _health_b) in (
|
||||
for (_entity_b, interpolated_b, body_b, _health_b) in (
|
||||
&ecs.entities(),
|
||||
&ecs.read_storage::<Pos>(),
|
||||
&ecs.read_storage::<Interpolated>(),
|
||||
&ecs.read_storage::<Body>(),
|
||||
&ecs.read_storage::<comp::Health>(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(e, _, _, h)| !h.is_dead && entity != *e)
|
||||
{
|
||||
if pos.0.distance_squared(pos_b.0) < range.powi(2) {
|
||||
if interpolated.pos.distance_squared(interpolated_b.pos)
|
||||
< range.powi(2)
|
||||
{
|
||||
let heartbeats = self
|
||||
.scheduler
|
||||
.heartbeats(Duration::from_millis(20));
|
||||
@ -655,7 +664,7 @@ impl ParticleMgr {
|
||||
* usize::from(heartbeats)
|
||||
/ 150,
|
||||
|| {
|
||||
let start_pos = pos_b.0
|
||||
let start_pos = interpolated_b.pos
|
||||
+ Vec3::unit_z() * body_b.height() * 0.5
|
||||
+ Vec3::<f32>::zero()
|
||||
.map(|_| rng.gen_range(-1.0..1.0))
|
||||
@ -666,7 +675,7 @@ impl ParticleMgr {
|
||||
time,
|
||||
ParticleMode::CultistFlame,
|
||||
start_pos,
|
||||
pos.0
|
||||
interpolated.pos
|
||||
+ Vec3::unit_z() * body.height() * 0.5,
|
||||
)
|
||||
},
|
||||
@ -683,8 +692,9 @@ impl ParticleMgr {
|
||||
self.particles.len()
|
||||
+ usize::from(self.scheduler.heartbeats(Duration::from_millis(10))),
|
||||
|| {
|
||||
let center_pos = pos.0 + Vec3::unit_z() * body.height() / 2.0;
|
||||
let outer_pos = pos.0
|
||||
let center_pos =
|
||||
interpolated.pos + Vec3::unit_z() * body.height() / 2.0;
|
||||
let outer_pos = interpolated.pos
|
||||
+ Vec3::new(
|
||||
2.0 * rng.gen::<f32>() - 1.0,
|
||||
2.0 * rng.gen::<f32>() - 1.0,
|
||||
@ -721,14 +731,15 @@ impl ParticleMgr {
|
||||
self.scheduler.heartbeats(Duration::from_millis(5)),
|
||||
),
|
||||
|| {
|
||||
let start_pos = pos.0
|
||||
let start_pos = interpolated.pos
|
||||
+ Vec3::new(
|
||||
body.max_radius(),
|
||||
body.max_radius(),
|
||||
body.height() / 2.0,
|
||||
)
|
||||
.map(|d| d * rng.gen_range(-1.0..1.0));
|
||||
let end_pos = pos.0 + (start_pos - pos.0) * 6.0;
|
||||
let end_pos =
|
||||
interpolated.pos + (start_pos - interpolated.pos) * 6.0;
|
||||
Particle::new_directed(
|
||||
Duration::from_secs(1),
|
||||
time,
|
||||
@ -752,14 +763,18 @@ impl ParticleMgr {
|
||||
let time = state.get_time();
|
||||
let dt = scene_data.state.ecs().fetch::<DeltaTime>().0;
|
||||
|
||||
for (pos, ori, beam) in (
|
||||
for (interp, pos, ori, beam) in (
|
||||
ecs.read_storage::<Interpolated>().maybe(),
|
||||
&ecs.read_storage::<Pos>(),
|
||||
&ecs.read_storage::<Ori>(),
|
||||
&ecs.read_storage::<BeamSegment>(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(_, _, b)| b.creation.map_or(true, |c| (c + dt as f64) >= time))
|
||||
.filter(|(_, _, _, b)| b.creation.map_or(true, |c| (c + dt as f64) >= time))
|
||||
{
|
||||
let pos = interp.map_or(pos.0, |i| i.pos);
|
||||
let ori = interp.map_or(*ori, |i| i.ori);
|
||||
|
||||
// TODO: Handle this less hackily. Done this way as beam segments are created
|
||||
// every server tick, which is approximately 33 ms. Heartbeat scheduler used to
|
||||
// account for clients with less than 30 fps because they start the creation
|
||||
@ -773,7 +788,7 @@ impl ParticleMgr {
|
||||
let m = Mat3::<f32>::rotation_from_to_3d(from, to);
|
||||
// Emit a light when using flames
|
||||
lights.push(Light::new(
|
||||
pos.0,
|
||||
pos,
|
||||
Rgb::new(1.0, 0.25, 0.05).map(|e| e * rng.gen_range(0.8..1.2)),
|
||||
2.0,
|
||||
));
|
||||
@ -792,8 +807,8 @@ impl ParticleMgr {
|
||||
beam.properties.duration,
|
||||
time,
|
||||
ParticleMode::FlameThrower,
|
||||
pos.0,
|
||||
pos.0 + random_ori * range,
|
||||
pos,
|
||||
pos + random_ori * range,
|
||||
)
|
||||
},
|
||||
);
|
||||
@ -804,7 +819,7 @@ impl ParticleMgr {
|
||||
let m = Mat3::<f32>::rotation_from_to_3d(from, to);
|
||||
// Emit a light when using flames
|
||||
lights.push(Light::new(
|
||||
pos.0,
|
||||
pos,
|
||||
Rgb::new(1.0, 0.0, 1.0).map(|e| e * rng.gen_range(0.5..1.0)),
|
||||
2.0,
|
||||
));
|
||||
@ -823,23 +838,23 @@ impl ParticleMgr {
|
||||
beam.properties.duration,
|
||||
time,
|
||||
ParticleMode::CultistFlame,
|
||||
pos.0,
|
||||
pos.0 + random_ori * range,
|
||||
pos,
|
||||
pos + random_ori * range,
|
||||
)
|
||||
},
|
||||
);
|
||||
},
|
||||
beam::FrontendSpecifier::LifestealBeam => {
|
||||
// Emit a light when using lifesteal beam
|
||||
lights.push(Light::new(pos.0, Rgb::new(0.8, 1.0, 0.5), 1.0));
|
||||
lights.push(Light::new(pos, Rgb::new(0.8, 1.0, 0.5), 1.0));
|
||||
self.particles.reserve(beam_tick_count as usize);
|
||||
for i in 0..beam_tick_count {
|
||||
self.particles.push(Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
time + i as f64 / 1000.0,
|
||||
ParticleMode::LifestealBeam,
|
||||
pos.0,
|
||||
pos.0 + *ori.look_dir() * range,
|
||||
pos,
|
||||
pos + *ori.look_dir() * range,
|
||||
));
|
||||
}
|
||||
},
|
||||
@ -849,8 +864,8 @@ impl ParticleMgr {
|
||||
beam.properties.duration,
|
||||
time,
|
||||
ParticleMode::Laser,
|
||||
pos.0,
|
||||
pos.0 + *ori.look_dir() * range,
|
||||
pos,
|
||||
pos + *ori.look_dir() * range,
|
||||
)
|
||||
})
|
||||
},
|
||||
@ -860,8 +875,8 @@ impl ParticleMgr {
|
||||
beam.properties.duration,
|
||||
time,
|
||||
ParticleMode::WebStrand,
|
||||
pos.0,
|
||||
pos.0 + *ori.look_dir() * range,
|
||||
pos,
|
||||
pos + *ori.look_dir() * range,
|
||||
)
|
||||
})
|
||||
},
|
||||
@ -884,8 +899,8 @@ impl ParticleMgr {
|
||||
beam.properties.duration,
|
||||
time,
|
||||
ParticleMode::Bubbles,
|
||||
pos.0,
|
||||
pos.0 + random_ori * range,
|
||||
pos,
|
||||
pos + random_ori * range,
|
||||
)
|
||||
},
|
||||
);
|
||||
@ -909,8 +924,8 @@ impl ParticleMgr {
|
||||
beam.properties.duration,
|
||||
time,
|
||||
ParticleMode::Ice,
|
||||
pos.0,
|
||||
pos.0 + random_ori * range,
|
||||
pos,
|
||||
pos + random_ori * range,
|
||||
)
|
||||
},
|
||||
);
|
||||
@ -926,12 +941,15 @@ impl ParticleMgr {
|
||||
let mut rng = thread_rng();
|
||||
let dt = scene_data.state.get_delta_time();
|
||||
|
||||
for (pos, auras) in (
|
||||
for (interp, pos, auras) in (
|
||||
ecs.read_storage::<Interpolated>().maybe(),
|
||||
&ecs.read_storage::<Pos>(),
|
||||
&ecs.read_storage::<comp::Auras>(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
let pos = interp.map_or(pos.0, |i| i.pos);
|
||||
|
||||
for (_, aura) in auras.auras.iter() {
|
||||
match aura.aura_kind {
|
||||
aura::AuraKind::Buff {
|
||||
@ -950,8 +968,8 @@ impl ParticleMgr {
|
||||
aura.duration.map_or(max_dur, |dur| dur.min(max_dur)),
|
||||
time,
|
||||
ParticleMode::EnergyNature,
|
||||
pos.0,
|
||||
pos.0 + init_pos,
|
||||
pos,
|
||||
pos + init_pos,
|
||||
)
|
||||
},
|
||||
);
|
||||
@ -982,8 +1000,8 @@ impl ParticleMgr {
|
||||
aura.duration.map_or(max_dur, |dur| dur.min(max_dur)),
|
||||
time,
|
||||
ParticleMode::EnergyHealing,
|
||||
pos.0,
|
||||
pos.0 + init_pos,
|
||||
pos,
|
||||
pos + init_pos,
|
||||
)
|
||||
},
|
||||
);
|
||||
@ -1006,15 +1024,15 @@ impl ParticleMgr {
|
||||
let radius = aura.radius * rng.gen::<f32>().sqrt();
|
||||
let x = radius * theta.sin();
|
||||
let y = radius * theta.cos();
|
||||
Vec2::new(x, y) + pos.0.xy()
|
||||
Vec2::new(x, y) + pos.xy()
|
||||
};
|
||||
let max_dur = Duration::from_secs(1);
|
||||
Particle::new_directed(
|
||||
aura.duration.map_or(max_dur, |dur| dur.min(max_dur)),
|
||||
time,
|
||||
ParticleMode::FlameThrower,
|
||||
rand_pos.with_z(pos.0.z),
|
||||
rand_pos.with_z(pos.0.z + 1.0),
|
||||
rand_pos.with_z(pos.z),
|
||||
rand_pos.with_z(pos.z + 1.0),
|
||||
)
|
||||
});
|
||||
},
|
||||
@ -1034,8 +1052,8 @@ impl ParticleMgr {
|
||||
aura.duration.map_or(max_dur, |dur| dur.min(max_dur)),
|
||||
time,
|
||||
ParticleMode::EnergyBuffing,
|
||||
pos.0,
|
||||
pos.0 + init_pos,
|
||||
pos,
|
||||
pos + init_pos,
|
||||
)
|
||||
},
|
||||
);
|
||||
@ -1052,13 +1070,16 @@ impl ParticleMgr {
|
||||
let time = state.get_time();
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (pos, buffs, body) in (
|
||||
for (interp, pos, buffs, body) in (
|
||||
ecs.read_storage::<Interpolated>().maybe(),
|
||||
&ecs.read_storage::<Pos>(),
|
||||
&ecs.read_storage::<comp::Buffs>(),
|
||||
&ecs.read_storage::<comp::Body>(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
let pos = interp.map_or(pos.0, |i| i.pos);
|
||||
|
||||
for (buff_kind, _) in buffs.kinds.iter() {
|
||||
use buff::BuffKind;
|
||||
match buff_kind {
|
||||
@ -1067,7 +1088,7 @@ impl ParticleMgr {
|
||||
self.particles.len()
|
||||
+ usize::from(self.scheduler.heartbeats(Duration::from_millis(15))),
|
||||
|| {
|
||||
let start_pos = pos.0
|
||||
let start_pos = pos
|
||||
+ Vec3::unit_z() * body.height() * 0.25
|
||||
+ Vec3::<f32>::zero()
|
||||
.map(|_| rng.gen_range(-1.0..1.0))
|
||||
@ -1097,7 +1118,7 @@ impl ParticleMgr {
|
||||
self.particles.len()
|
||||
+ usize::from(self.scheduler.heartbeats(Duration::from_millis(15))),
|
||||
|| {
|
||||
let start_pos = pos.0
|
||||
let start_pos = pos
|
||||
+ Vec3::new(
|
||||
body.max_radius(),
|
||||
body.max_radius(),
|
||||
@ -1139,9 +1160,10 @@ impl ParticleMgr {
|
||||
let time = scene_data.state.get_time();
|
||||
let player_pos = scene_data
|
||||
.state
|
||||
.read_component_copied::<Pos>(scene_data.player_entity)
|
||||
.read_component_copied::<Interpolated>(scene_data.player_entity)
|
||||
.map(|i| i.pos)
|
||||
.unwrap_or_default();
|
||||
let player_chunk = player_pos.0.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
let player_chunk = player_pos.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
(e.floor() as i32).div_euclid(sz as i32)
|
||||
});
|
||||
|
||||
@ -1350,14 +1372,18 @@ impl ParticleMgr {
|
||||
let dt = scene_data.state.ecs().fetch::<DeltaTime>().0;
|
||||
let terrain = scene_data.state.ecs().fetch::<TerrainGrid>();
|
||||
|
||||
for (_entity, pos, ori, shockwave) in (
|
||||
for (_entity, interp, pos, ori, shockwave) in (
|
||||
&ecs.entities(),
|
||||
ecs.read_storage::<Interpolated>().maybe(),
|
||||
&ecs.read_storage::<Pos>(),
|
||||
&ecs.read_storage::<Ori>(),
|
||||
&ecs.read_storage::<Shockwave>(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
let pos = interp.map_or(pos.0, |i| i.pos);
|
||||
let ori = interp.map_or(*ori, |i| i.ori);
|
||||
|
||||
let elapsed = time - shockwave.creation.unwrap_or(time);
|
||||
let speed = shockwave.properties.speed;
|
||||
|
||||
@ -1396,7 +1422,7 @@ impl ParticleMgr {
|
||||
for d in 0..(new_particle_count as i32) {
|
||||
let arc_position = theta + dtheta * d as f32 / particle_count_factor;
|
||||
|
||||
let position = pos.0
|
||||
let position = pos
|
||||
+ distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
|
||||
|
||||
// Arbitrary number chosen that is large enough to be able to accurately
|
||||
@ -1446,7 +1472,7 @@ impl ParticleMgr {
|
||||
for d in 0..3 * distance as i32 {
|
||||
let arc_position = theta + dtheta * d as f32 / 3.0;
|
||||
|
||||
let position = pos.0
|
||||
let position = pos
|
||||
+ distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
|
||||
|
||||
self.particles.push(Particle::new(
|
||||
@ -1479,7 +1505,7 @@ impl ParticleMgr {
|
||||
// Sub tick dt
|
||||
let dt = (j as f32 / heartbeats as f32) * dt;
|
||||
let distance = distance + speed * dt;
|
||||
let pos1 = pos.0 + distance * direction - Vec3::unit_z();
|
||||
let pos1 = pos + distance * direction - Vec3::unit_z();
|
||||
let pos2 = pos1 + (Vec3::unit_z() + direction) * 3.0;
|
||||
let time = time + dt as f64;
|
||||
|
||||
@ -1522,7 +1548,7 @@ impl ParticleMgr {
|
||||
// Sub tick dt
|
||||
let dt = (j as f32 / heartbeats as f32) * dt;
|
||||
let scaled_distance = scaled_distance + scaled_speed * dt;
|
||||
let pos1 = pos.0 + (scaled_distance * direction).floor() * scale;
|
||||
let pos1 = pos + (scaled_distance * direction).floor() * scale;
|
||||
let time = time + dt as f64;
|
||||
|
||||
let get_positions = |a| {
|
||||
|
@ -94,8 +94,8 @@ impl Civs {
|
||||
let initial_civ_count = initial_civ_count(sim.map_size_lg());
|
||||
let mut ctx = GenCtx { sim, rng };
|
||||
|
||||
info!("starting cave generation");
|
||||
this.generate_caves(&mut ctx);
|
||||
// info!("starting cave generation");
|
||||
// this.generate_caves(&mut ctx);
|
||||
|
||||
info!("starting civilisation creation");
|
||||
let mut start_locations: Vec<Vec2<i32>> = Vec::new();
|
||||
@ -438,7 +438,7 @@ impl Civs {
|
||||
&& chunk.alt < cave_max_alt
|
||||
&& cave_min_alt < chunk.water_alt
|
||||
&& chunk.river.near_water()
|
||||
// Only do this for caves at the sea level for now.
|
||||
// Only do this for caves at the sea level for now.
|
||||
// The reason being that floodfilling from a water alt to an alt lower than the water alt causes problems.
|
||||
&& chunk.water_alt <= CONFIG.sea_level;
|
||||
if submerged {
|
||||
|
@ -9,7 +9,7 @@ use common::{
|
||||
trade::{SiteId, SitePrices},
|
||||
};
|
||||
use core::ops::Deref;
|
||||
use noise::{Seedable, SuperSimplex};
|
||||
use noise::{Fbm, Seedable, SuperSimplex};
|
||||
use std::sync::Arc;
|
||||
|
||||
const WORLD_COLORS_MANIFEST: &str = "world.style.colors";
|
||||
@ -136,6 +136,7 @@ impl IndexOwned {
|
||||
pub struct Noise {
|
||||
pub cave_nz: SuperSimplex,
|
||||
pub scatter_nz: SuperSimplex,
|
||||
pub cave_fbm_nz: Fbm,
|
||||
}
|
||||
|
||||
impl Noise {
|
||||
@ -143,6 +144,7 @@ impl Noise {
|
||||
Self {
|
||||
cave_nz: SuperSimplex::new().set_seed(seed + 0),
|
||||
scatter_nz: SuperSimplex::new().set_seed(seed + 1),
|
||||
cave_fbm_nz: Fbm::new().set_seed(seed + 2),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ pub struct Land<'a> {
|
||||
impl<'a> Land<'a> {
|
||||
pub fn empty() -> Self { Self { sim: None } }
|
||||
|
||||
pub fn size(&self) -> Vec2<u32> { self.sim.map_or(Vec2::one(), |s| s.get_size()) }
|
||||
|
||||
pub fn from_sim(sim: &'a sim::WorldSim) -> Self { Self { sim: Some(sim) } }
|
||||
|
||||
pub fn get_alt_approx(&self, wpos: Vec2<i32>) -> f32 {
|
||||
|
947
world/src/layer/cave.rs
Normal file
947
world/src/layer/cave.rs
Normal file
@ -0,0 +1,947 @@
|
||||
use super::scatter::close;
|
||||
|
||||
use crate::{
|
||||
util::{sampler::Sampler, FastNoise, RandomField, RandomPerm, StructureGen2d, LOCALITY},
|
||||
Canvas, CanvasInfo, ColumnSample, Land,
|
||||
};
|
||||
use common::{
|
||||
generation::EntityInfo,
|
||||
terrain::{
|
||||
quadratic_nearest_point, river_spline_coeffs, Block, BlockKind, SpriteKind,
|
||||
TerrainChunkSize,
|
||||
},
|
||||
vol::RectVolSize,
|
||||
};
|
||||
use noise::NoiseFn;
|
||||
use rand::prelude::*;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
f64::consts::PI,
|
||||
ops::{Add, Mul, Range, Sub},
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
const CELL_SIZE: i32 = 1536;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Node {
|
||||
pub wpos: Vec2<i32>,
|
||||
pub depth: i32,
|
||||
}
|
||||
|
||||
fn to_cell(wpos: Vec2<i32>, level: u32) -> Vec2<i32> {
|
||||
(wpos + (level & 1) as i32 * CELL_SIZE / 4).map(|e| e.div_euclid(CELL_SIZE))
|
||||
}
|
||||
fn to_wpos(cell: Vec2<i32>, level: u32) -> Vec2<i32> {
|
||||
(cell * CELL_SIZE) - (level & 1) as i32 * CELL_SIZE / 4
|
||||
}
|
||||
|
||||
const AVG_LEVEL_DEPTH: i32 = 120;
|
||||
const LAYERS: u32 = 4;
|
||||
|
||||
fn node_at(cell: Vec2<i32>, level: u32, land: &Land) -> Option<Node> {
|
||||
let rand = RandomField::new(37 + level);
|
||||
|
||||
if rand.chance(cell.with_z(0), 0.75) || level == 0 {
|
||||
let dx = RandomField::new(38 + level);
|
||||
let dy = RandomField::new(39 + level);
|
||||
let wpos = to_wpos(cell, level)
|
||||
+ CELL_SIZE as i32 / 4
|
||||
+ (Vec2::new(dx.get(cell.with_z(0)), dy.get(cell.with_z(0))) % CELL_SIZE as u32 / 2)
|
||||
.map(|e| e as i32);
|
||||
land.get_chunk_wpos(wpos).and_then(|chunk| {
|
||||
let depth = AVG_LEVEL_DEPTH * level as i32 - 6;
|
||||
|
||||
if level > 0
|
||||
|| (!chunk.near_cliffs()
|
||||
&& !chunk.river.near_water()
|
||||
&& chunk.sites.is_empty()
|
||||
&& land.get_gradient_approx(wpos) < 0.75)
|
||||
{
|
||||
Some(Node { wpos, depth })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn surface_entrances<'a>(land: &'a Land) -> impl Iterator<Item = Vec2<i32>> + 'a {
|
||||
let sz_cells = to_cell(
|
||||
land.size()
|
||||
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| (e * sz) as i32),
|
||||
0,
|
||||
);
|
||||
(0..sz_cells.x + 1)
|
||||
.flat_map(move |x| (0..sz_cells.y + 1).map(move |y| Vec2::new(x, y)))
|
||||
.filter_map(|cell| Some(tunnel_below_from_cell(cell, 0, land)?.a.wpos))
|
||||
}
|
||||
|
||||
pub struct Tunnel {
|
||||
a: Node,
|
||||
b: Node,
|
||||
curve: f32,
|
||||
}
|
||||
|
||||
impl Tunnel {
|
||||
fn ctrl_offset(&self) -> Vec2<f32> {
|
||||
let start = self.a.wpos.map(|e| e as f64 + 0.5);
|
||||
let end = self.b.wpos.map(|e| e as f64 + 0.5);
|
||||
|
||||
((end - start) * 0.5 + ((end - start) * 0.5).rotated_z(PI / 2.0) * 6.0 * self.curve as f64)
|
||||
.map(|e| e as f32)
|
||||
}
|
||||
|
||||
fn z_range_at(&self, wposf: Vec2<f64>, info: CanvasInfo) -> Option<(Range<i32>, f64)> {
|
||||
let start = self.a.wpos.map(|e| e as f64 + 0.5);
|
||||
let end = self.b.wpos.map(|e| e as f64 + 0.5);
|
||||
|
||||
if let Some((t, closest, _)) = quadratic_nearest_point(
|
||||
&river_spline_coeffs(start, self.ctrl_offset(), end),
|
||||
wposf,
|
||||
Vec2::new(start, end),
|
||||
) {
|
||||
let dist = closest.distance(wposf);
|
||||
let radius = 8.0..64.0;
|
||||
if dist < radius.end + 1.0 {
|
||||
let radius = Lerp::lerp(
|
||||
radius.start,
|
||||
radius.end,
|
||||
(info.index().noise.cave_fbm_nz.get(
|
||||
(wposf.with_z(info.land().get_alt_approx(self.a.wpos) as f64) / 200.0)
|
||||
.into_array(),
|
||||
) * 2.0
|
||||
* 0.5
|
||||
+ 0.5)
|
||||
.clamped(0.0, 1.0)
|
||||
.powf(3.0),
|
||||
);
|
||||
let height_here = (1.0 - dist / radius).max(0.0).powf(0.3) * radius;
|
||||
if height_here > 0.0 {
|
||||
let z_offs = info
|
||||
.index()
|
||||
.noise
|
||||
.cave_fbm_nz
|
||||
.get((wposf / 512.0).into_array())
|
||||
* 96.0
|
||||
* ((1.0 - (t - 0.5).abs() * 2.0) * 8.0).min(1.0);
|
||||
let alt_here = info.land().get_alt_approx(closest.map(|e| e as i32));
|
||||
let base = Lerp::lerp(
|
||||
alt_here as f64 - self.a.depth as f64,
|
||||
alt_here as f64 - self.b.depth as f64,
|
||||
t,
|
||||
) + z_offs;
|
||||
Some((
|
||||
(base - height_here * 0.3) as i32..(base + height_here * 1.35) as i32,
|
||||
radius,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn biome_at(&self, wpos: Vec3<i32>, info: &CanvasInfo) -> Biome {
|
||||
let Some(col) = info.col_or_gen(wpos.xy()) else { return Biome::default() };
|
||||
|
||||
// Below the ground
|
||||
let below = ((col.alt - wpos.z as f32) / 120.0).clamped(0.0, 1.0);
|
||||
let depth = (col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32);
|
||||
|
||||
let humidity = Lerp::lerp(
|
||||
col.humidity,
|
||||
info.index()
|
||||
.noise
|
||||
.cave_nz
|
||||
.get(wpos.xy().map(|e| e as f64 / 1024.0).into_array()) as f32,
|
||||
below,
|
||||
);
|
||||
let temp = Lerp::lerp(
|
||||
col.temp,
|
||||
info.index()
|
||||
.noise
|
||||
.cave_nz
|
||||
.get(wpos.xy().map(|e| e as f64 / 2048.0).into_array())
|
||||
.mul(2.0)
|
||||
.sub(1.0)
|
||||
.add(
|
||||
((col.alt as f64 - wpos.z as f64)
|
||||
/ (AVG_LEVEL_DEPTH as f64 * LAYERS as f64 * 0.5))
|
||||
.clamped(0.0, 2.5),
|
||||
) as f32,
|
||||
below,
|
||||
);
|
||||
let mineral = info
|
||||
.index()
|
||||
.noise
|
||||
.cave_nz
|
||||
.get(wpos.xy().map(|e| e as f64 / 256.0).into_array())
|
||||
.mul(0.5)
|
||||
.add(0.5) as f32;
|
||||
|
||||
let underground = ((col.alt as f32 - wpos.z as f32) / 80.0 - 1.0).clamped(0.0, 1.0);
|
||||
|
||||
let [barren, mushroom, fire, leafy, dusty, icy] = {
|
||||
let barren = 0.01;
|
||||
let mushroom = underground * close(humidity, 1.0, 0.75) * close(temp, 0.0, 0.9);
|
||||
let fire = underground
|
||||
* close(humidity, 0.0, 0.9)
|
||||
* close(temp, 2.0, 1.0)
|
||||
* close(depth, 1.0, 0.65);
|
||||
let leafy = underground * close(humidity, 1.0, 0.85) * close(temp, 0.45, 0.8);
|
||||
let dusty = close(humidity, 0.0, 0.5) * close(temp, -0.3, 0.5);
|
||||
let icy = close(temp, -1.0, 0.3);
|
||||
|
||||
let biomes = [barren, mushroom, fire, leafy, dusty, icy];
|
||||
let max = biomes
|
||||
.into_iter()
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal))
|
||||
.unwrap();
|
||||
biomes.map(|e| (e / max).powf(3.0))
|
||||
};
|
||||
|
||||
Biome {
|
||||
humidity,
|
||||
mineral,
|
||||
barren,
|
||||
mushroom,
|
||||
fire,
|
||||
leafy,
|
||||
dusty,
|
||||
icy,
|
||||
depth,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tunnels_at<'a>(
|
||||
wpos: Vec2<i32>,
|
||||
level: u32,
|
||||
land: &'a Land,
|
||||
) -> impl Iterator<Item = Tunnel> + 'a {
|
||||
let rand = RandomField::new(37 + level);
|
||||
let col_cell = to_cell(wpos - CELL_SIZE as i32 / 4, level);
|
||||
LOCALITY
|
||||
.into_iter()
|
||||
.filter_map(move |rpos| {
|
||||
let current_cell_pos = col_cell + rpos;
|
||||
Some(current_cell_pos).zip(node_at(current_cell_pos, level, land))
|
||||
})
|
||||
.flat_map(move |(current_cell_pos, current_cell)| {
|
||||
[Vec2::new(1, 1), Vec2::new(1, -1)]
|
||||
.into_iter()
|
||||
.filter(move |rpos| {
|
||||
let mid = (current_cell_pos * 2 + rpos) / 2;
|
||||
rand.chance(mid.with_z(0), 0.5) ^ (rpos.y == -1)
|
||||
})
|
||||
.chain([Vec2::new(1, 0), Vec2::new(0, 1)])
|
||||
.filter_map(move |rpos| {
|
||||
let other_cell_pos = current_cell_pos + rpos;
|
||||
Some(other_cell_pos).zip(node_at(other_cell_pos, level, land))
|
||||
})
|
||||
.filter(move |(other_cell_pos, _)| {
|
||||
rand.chance((current_cell_pos + other_cell_pos).with_z(7), 0.3)
|
||||
})
|
||||
.map(move |(_other_cell_pos, other_cell)| Tunnel {
|
||||
a: current_cell,
|
||||
b: other_cell,
|
||||
curve: RandomField::new(13)
|
||||
.get_f32(current_cell.wpos.with_z(0))
|
||||
.powf(0.25)
|
||||
.mul(
|
||||
if RandomField::new(14).chance(current_cell.wpos.with_z(0), 0.5) {
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
},
|
||||
),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn tunnel_below_from_cell(cell: Vec2<i32>, level: u32, land: &Land) -> Option<Tunnel> {
|
||||
let wpos = to_wpos(cell, level);
|
||||
Some(Tunnel {
|
||||
a: node_at(to_cell(wpos, level), level, land)?,
|
||||
b: node_at(
|
||||
to_cell(wpos + CELL_SIZE as i32 / 2, level + 1),
|
||||
level + 1,
|
||||
land,
|
||||
)?,
|
||||
curve: 0.0,
|
||||
})
|
||||
}
|
||||
|
||||
fn tunnels_down_from<'a>(
|
||||
wpos: Vec2<i32>,
|
||||
level: u32,
|
||||
land: &'a Land,
|
||||
) -> impl Iterator<Item = Tunnel> + 'a {
|
||||
let col_cell = to_cell(wpos, level);
|
||||
LOCALITY
|
||||
.into_iter()
|
||||
.filter_map(move |rpos| tunnel_below_from_cell(col_cell + rpos, level, land))
|
||||
}
|
||||
|
||||
pub fn tunnel_bounds_at<'a>(
|
||||
wpos2d: Vec2<i32>,
|
||||
info: &'a CanvasInfo,
|
||||
land: &'a Land,
|
||||
) -> impl Iterator<Item = (u32, Range<i32>, f64, Tunnel)> + 'a {
|
||||
let wposf = wpos2d.map(|e| e as f64 + 0.5);
|
||||
info.col_or_gen(wpos2d).into_iter().flat_map(move |col| {
|
||||
let col_alt = col.alt;
|
||||
let col_water_dist = col.water_dist;
|
||||
(1..LAYERS + 1).flat_map(move |level| {
|
||||
tunnels_at(wpos2d, level, land)
|
||||
.chain(tunnels_down_from(wpos2d, level - 1, land))
|
||||
.filter_map(move |tunnel| {
|
||||
let (z_range, radius) = tunnel.z_range_at(wposf, *info)?;
|
||||
// Avoid cave entrances intersecting water
|
||||
let z_range = Lerp::lerp(
|
||||
z_range.end,
|
||||
z_range.start,
|
||||
1.0 - (1.0
|
||||
- ((col_water_dist.unwrap_or(1000.0) - 4.0).max(0.0) / 32.0)
|
||||
.clamped(0.0, 1.0))
|
||||
* (1.0
|
||||
- ((col_alt - z_range.end as f32 - 4.0) / 8.0).clamped(0.0, 1.0)),
|
||||
)..z_range.end;
|
||||
if z_range.end - z_range.start > 0 {
|
||||
Some((level, z_range, radius, tunnel))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
|
||||
let info = canvas.info();
|
||||
let mut mushroom_cache = HashMap::new();
|
||||
canvas.foreach_col(|canvas, wpos2d, col| {
|
||||
let land = info.land();
|
||||
|
||||
let tunnel_bounds = tunnel_bounds_at(wpos2d, &info, &land).collect::<Vec<_>>();
|
||||
|
||||
// First, clear out tunnels
|
||||
for (_, z_range, _, _) in &tunnel_bounds {
|
||||
for z in z_range.start..z_range.end.min(col.alt as i32 + 1) {
|
||||
canvas.set(wpos2d.with_z(z), Block::air(SpriteKind::Empty));
|
||||
}
|
||||
}
|
||||
|
||||
for (level, z_range, _radius, tunnel) in tunnel_bounds {
|
||||
write_column(
|
||||
canvas,
|
||||
col,
|
||||
level,
|
||||
wpos2d,
|
||||
z_range.clone(),
|
||||
tunnel,
|
||||
&mut mushroom_cache,
|
||||
rng,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Biome {
|
||||
humidity: f32,
|
||||
barren: f32,
|
||||
mineral: f32,
|
||||
mushroom: f32,
|
||||
fire: f32,
|
||||
leafy: f32,
|
||||
dusty: f32,
|
||||
icy: f32,
|
||||
depth: f32,
|
||||
}
|
||||
|
||||
struct Mushroom {
|
||||
pos: Vec3<i32>,
|
||||
stalk: f32,
|
||||
head_color: Rgb<u8>,
|
||||
}
|
||||
|
||||
fn write_column<R: Rng>(
|
||||
canvas: &mut Canvas,
|
||||
col: &ColumnSample,
|
||||
level: u32,
|
||||
wpos2d: Vec2<i32>,
|
||||
z_range: Range<i32>,
|
||||
tunnel: Tunnel,
|
||||
mushroom_cache: &mut HashMap<(Vec3<i32>, Vec2<i32>), Option<Mushroom>>,
|
||||
rng: &mut R,
|
||||
) {
|
||||
mushroom_cache.clear();
|
||||
let info = canvas.info();
|
||||
|
||||
// Exposed to the sky, or some other void above
|
||||
let void_above = !canvas.get(wpos2d.with_z(z_range.end)).is_filled();
|
||||
let void_below = !canvas.get(wpos2d.with_z(z_range.start - 1)).is_filled();
|
||||
// Exposed to the sky
|
||||
let sky_above = z_range.end as f32 > col.alt;
|
||||
|
||||
let biome = tunnel.biome_at(wpos2d.with_z(z_range.start), &info);
|
||||
|
||||
let stalactite = {
|
||||
let cavern_height = (z_range.end - z_range.start) as f64;
|
||||
info
|
||||
.index()
|
||||
.noise
|
||||
.cave_nz
|
||||
.get(wpos2d.map(|e| e as f64 / 16.0).into_array())
|
||||
.sub(0.5)
|
||||
.max(0.0)
|
||||
.mul(2.0)
|
||||
// No stalactites near entrances
|
||||
.mul(((col.alt as f64 - z_range.end as f64) / 8.0).clamped(0.0, 1.0))
|
||||
.mul(8.0 + cavern_height * 0.4)
|
||||
};
|
||||
|
||||
let basalt = if biome.fire > 0.0 {
|
||||
let cavern_height = (z_range.end - z_range.start) as f64;
|
||||
info.index()
|
||||
.noise
|
||||
.cave_nz
|
||||
.get(wpos2d.map(|e| e as f64 / 48.0).into_array())
|
||||
.sub(0.5)
|
||||
.max(0.0)
|
||||
.mul(6.0 + cavern_height * 0.5)
|
||||
.mul(biome.fire as f64)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let lava = if biome.fire > 0.0 {
|
||||
info.index()
|
||||
.noise
|
||||
.cave_nz
|
||||
.get(wpos2d.map(|e| e as f64 / 64.0).into_array())
|
||||
.sub(0.5)
|
||||
.abs()
|
||||
.sub(0.2)
|
||||
.min(0.0)
|
||||
// .mul((biome.temp as f64 - 1.5).mul(30.0).clamped(0.0, 1.0))
|
||||
.mul((biome.fire as f64 - 0.5).mul(30.0).clamped(0.0, 1.0))
|
||||
.mul(64.0)
|
||||
.max(-32.0)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let rand = RandomField::new(37 + level);
|
||||
|
||||
let is_ice = biome.icy + col.marble * 0.2 > 0.5 && col.marble > 0.6;
|
||||
|
||||
let dirt = 1 + (!is_ice) as i32;
|
||||
let bedrock = z_range.start + lava as i32;
|
||||
let base = bedrock + (stalactite * 0.4) as i32;
|
||||
let floor = base + dirt;
|
||||
let ceiling = z_range.end - stalactite as i32;
|
||||
|
||||
// Get mushroom block, if any, at a position
|
||||
let mut get_mushroom = |wpos: Vec3<i32>, dynamic_rng: &mut R| {
|
||||
for (wpos2d, seed) in StructureGen2d::new(34537, 24, 8).get(wpos.xy()) {
|
||||
let mushroom = if let Some(mushroom) = mushroom_cache
|
||||
.entry((tunnel.a.wpos.with_z(tunnel.a.depth), wpos2d))
|
||||
.or_insert_with(|| {
|
||||
let mut rng = RandomPerm::new(seed);
|
||||
let (z_range, radius) =
|
||||
tunnel.z_range_at(wpos2d.map(|e| e as f64 + 0.5), info)?;
|
||||
let pos = wpos2d.with_z(z_range.start);
|
||||
if rng.gen_bool(0.5 * close(radius as f32, 64.0, 48.0) as f64)
|
||||
&& tunnel.biome_at(pos, &info).mushroom > 0.5
|
||||
// Ensure that we're not placing the mushroom over a void
|
||||
&& !tunnel_bounds_at(pos.xy(), &info, &info.land())
|
||||
.any(|(_, z_range, _, _)| z_range.contains(&(z_range.start - 1)))
|
||||
// && pos.z as i32 > water_level - 2
|
||||
{
|
||||
let purp = rng.gen_range(0..50);
|
||||
Some(Mushroom {
|
||||
pos,
|
||||
stalk: 8.0
|
||||
+ rng.gen::<f32>().powf(2.0)
|
||||
* (z_range.end - z_range.start - 8) as f32
|
||||
* 0.75,
|
||||
head_color: Rgb::new(
|
||||
40 + purp,
|
||||
rng.gen_range(60..120),
|
||||
rng.gen_range(80..200) + purp,
|
||||
),
|
||||
})
|
||||
} 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 = 12.0f32;
|
||||
let head_height = 14.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
|
||||
};
|
||||
|
||||
for z in bedrock..z_range.end {
|
||||
let wpos = wpos2d.with_z(z);
|
||||
let mut try_spawn_entity = false;
|
||||
canvas.map(wpos, |_block| {
|
||||
if z < z_range.start - 4 && !void_below {
|
||||
Block::new(BlockKind::Lava, Rgb::new(255, 65, 0))
|
||||
} else if basalt > 0.0
|
||||
&& z < bedrock / 6 * 6
|
||||
+ 2
|
||||
+ basalt as i32 / 4 * 4
|
||||
+ (RandomField::new(77)
|
||||
.get_f32(((wpos2d + Vec2::new(wpos2d.y, -wpos2d.x) / 2) / 4).with_z(0))
|
||||
* 6.0)
|
||||
.floor() as i32
|
||||
&& !void_below
|
||||
{
|
||||
Block::new(BlockKind::Rock, Rgb::new(50, 35, 75))
|
||||
} else if (z < base && !void_below) || (z >= ceiling && !void_above) {
|
||||
let stalactite: Rgb<i16> = Lerp::lerp(
|
||||
Lerp::lerp(
|
||||
Lerp::lerp(Rgb::new(80, 100, 150), Rgb::new(0, 75, 200), biome.mushroom),
|
||||
Lerp::lerp(
|
||||
Rgb::new(100, 40, 40),
|
||||
Rgb::new(100, 75, 100),
|
||||
col.marble_small,
|
||||
),
|
||||
biome.fire,
|
||||
),
|
||||
Lerp::lerp(Rgb::new(100, 150, 255), Rgb::new(100, 120, 255), col.marble),
|
||||
biome.icy,
|
||||
);
|
||||
Block::new(
|
||||
if rand.chance(wpos, (biome.mushroom * biome.mineral).max(biome.icy)) {
|
||||
BlockKind::GlowingWeakRock
|
||||
} else {
|
||||
BlockKind::WeakRock
|
||||
},
|
||||
stalactite.map(|e| e as u8),
|
||||
)
|
||||
} else if z >= base && z < floor && !void_below && !sky_above {
|
||||
let (net_col, total) = [
|
||||
(
|
||||
Lerp::lerp(Rgb::new(40, 20, 0), Rgb::new(80, 80, 30), col.marble_small),
|
||||
0.05,
|
||||
),
|
||||
(
|
||||
Lerp::lerp(Rgb::new(50, 50, 75), Rgb::new(75, 75, 50), col.marble_mid),
|
||||
biome.dusty,
|
||||
),
|
||||
(
|
||||
Lerp::lerp(Rgb::new(20, 65, 175), Rgb::new(20, 100, 80), col.marble_mid),
|
||||
biome.mushroom,
|
||||
),
|
||||
(
|
||||
Lerp::lerp(Rgb::new(120, 50, 20), Rgb::new(50, 5, 40), col.marble_small),
|
||||
biome.fire,
|
||||
),
|
||||
(
|
||||
Lerp::lerp(
|
||||
Rgb::new(0, 100, 50),
|
||||
Rgb::new(80, 100, 20),
|
||||
col.marble_small,
|
||||
),
|
||||
biome.leafy,
|
||||
),
|
||||
(Rgb::new(170, 195, 255), biome.icy),
|
||||
]
|
||||
.into_iter()
|
||||
.fold((Rgb::<f32>::zero(), 0.0), |a, x| {
|
||||
(a.0 + x.0.map(|e| e as f32) * x.1, a.1 + x.1)
|
||||
});
|
||||
let surf_color = net_col.map(|e| (e / total) as u8);
|
||||
|
||||
if is_ice {
|
||||
Block::new(BlockKind::Ice, Rgb::new(120, 160, 255))
|
||||
} else {
|
||||
Block::new(
|
||||
if biome.mushroom.max(biome.leafy) > 0.5 {
|
||||
BlockKind::Grass
|
||||
} else if biome.icy > 0.5 {
|
||||
BlockKind::Snow
|
||||
} else if biome.fire > 0.5 {
|
||||
BlockKind::Rock
|
||||
} else {
|
||||
BlockKind::Sand
|
||||
},
|
||||
surf_color,
|
||||
)
|
||||
}
|
||||
} else if let Some(sprite) = (z == floor && !void_below && !sky_above)
|
||||
.then(|| {
|
||||
if rand.chance(wpos2d.with_z(1), biome.mushroom * 0.05) {
|
||||
[
|
||||
(SpriteKind::CaveMushroom, 0.15),
|
||||
(SpriteKind::Mushroom, 0.25),
|
||||
(SpriteKind::GrassBlue, 1.0),
|
||||
(SpriteKind::CavernGrassBlueShort, 1.0),
|
||||
(SpriteKind::CavernGrassBlueMedium, 1.0),
|
||||
(SpriteKind::CavernGrassBlueLong, 1.0),
|
||||
(SpriteKind::Moonbell, 0.01),
|
||||
]
|
||||
.choose_weighted(rng, |(_, w)| *w)
|
||||
.ok()
|
||||
.map(|s| s.0)
|
||||
} else if rand.chance(wpos2d.with_z(15), biome.leafy * 0.05) {
|
||||
[
|
||||
(SpriteKind::LongGrass, 1.0),
|
||||
(SpriteKind::MediumGrass, 2.0),
|
||||
(SpriteKind::ShortGrass, 2.0),
|
||||
(SpriteKind::JungleFern, 0.5),
|
||||
(SpriteKind::JungleLeafyPlant, 0.5),
|
||||
(SpriteKind::JungleRedGrass, 0.35),
|
||||
(SpriteKind::Mushroom, 0.15),
|
||||
(SpriteKind::EnsnaringVines, 0.2),
|
||||
(SpriteKind::Fern, 0.75),
|
||||
(SpriteKind::LeafyPlant, 0.8),
|
||||
(SpriteKind::Twigs, 0.07),
|
||||
(SpriteKind::Wood, 0.03),
|
||||
]
|
||||
.choose_weighted(rng, |(_, w)| *w)
|
||||
.ok()
|
||||
.map(|s| s.0)
|
||||
} else if rand.chance(wpos2d.with_z(2), biome.dusty * 0.01) {
|
||||
[
|
||||
(SpriteKind::Bones, 0.5),
|
||||
(SpriteKind::Stones, 1.5),
|
||||
(SpriteKind::DeadBush, 1.0),
|
||||
(SpriteKind::EnsnaringWeb, 0.5),
|
||||
(SpriteKind::Mud, 0.025),
|
||||
]
|
||||
.choose_weighted(rng, |(_, w)| *w)
|
||||
.ok()
|
||||
.map(|s| s.0)
|
||||
} else if rand.chance(wpos2d.with_z(14), biome.barren * 0.003) {
|
||||
[
|
||||
(SpriteKind::Welwitch, 0.5),
|
||||
(SpriteKind::DeadBush, 1.5),
|
||||
(SpriteKind::Crate, 0.005),
|
||||
]
|
||||
.choose_weighted(rng, |(_, w)| *w)
|
||||
.ok()
|
||||
.map(|s| s.0)
|
||||
} else if rand.chance(
|
||||
wpos2d.with_z(3),
|
||||
close(biome.humidity, 0.0, 0.5) * biome.mineral * 0.005,
|
||||
) {
|
||||
Some(SpriteKind::CrystalLow)
|
||||
} else if rand.chance(wpos2d.with_z(13), biome.fire * 0.001) {
|
||||
[
|
||||
(SpriteKind::Pyrebloom, 0.3),
|
||||
(SpriteKind::Bloodstone, 0.3),
|
||||
(SpriteKind::Gold, 0.15),
|
||||
]
|
||||
.choose_weighted(rng, |(_, w)| *w)
|
||||
.ok()
|
||||
.map(|s| s.0)
|
||||
} else if rand.chance(wpos2d.with_z(5), 0.0025) {
|
||||
[
|
||||
(Some(SpriteKind::VeloriteFrag), 0.3),
|
||||
(Some(SpriteKind::AmethystSmall), 0.3),
|
||||
(Some(SpriteKind::TopazSmall), 0.3),
|
||||
(Some(SpriteKind::DiamondSmall), 0.04),
|
||||
(Some(SpriteKind::RubySmall), 0.1),
|
||||
(Some(SpriteKind::EmeraldSmall), 0.08),
|
||||
(Some(SpriteKind::SapphireSmall), 0.08),
|
||||
(Some(SpriteKind::Velorite), 0.15),
|
||||
(Some(SpriteKind::Amethyst), 0.15),
|
||||
(Some(SpriteKind::Topaz), 0.15),
|
||||
(Some(SpriteKind::Diamond), 0.02),
|
||||
(Some(SpriteKind::Ruby), 0.05),
|
||||
(Some(SpriteKind::Emerald), 0.04),
|
||||
(Some(SpriteKind::Sapphire), 0.04),
|
||||
(None, 10.0),
|
||||
]
|
||||
.choose_weighted(rng, |(_, w)| *w)
|
||||
.ok()
|
||||
.and_then(|s| s.0)
|
||||
} else if rand.chance(wpos2d.with_z(6), 0.0002) {
|
||||
[
|
||||
(Some(SpriteKind::DungeonChest0), 1.0),
|
||||
(Some(SpriteKind::DungeonChest1), 0.3),
|
||||
(Some(SpriteKind::DungeonChest2), 0.1),
|
||||
(Some(SpriteKind::DungeonChest3), 0.03),
|
||||
(Some(SpriteKind::DungeonChest4), 0.01),
|
||||
(Some(SpriteKind::DungeonChest5), 0.003),
|
||||
(None, 1.0),
|
||||
]
|
||||
.choose_weighted(rng, |(_, w)| *w)
|
||||
.ok()
|
||||
.and_then(|s| s.0)
|
||||
} else if rand.chance(wpos2d.with_z(7), 0.01) {
|
||||
let shallow = close(biome.depth, 0.0, 0.4);
|
||||
let middle = close(biome.depth, 0.5, 0.4);
|
||||
//let deep = close(biome.depth, 1.0, 0.4); // TODO: Use this for deep only
|
||||
// things
|
||||
[
|
||||
(Some(SpriteKind::Stones), 1.5),
|
||||
(Some(SpriteKind::Copper), shallow),
|
||||
(Some(SpriteKind::Tin), shallow),
|
||||
(Some(SpriteKind::Iron), shallow * 0.5),
|
||||
(Some(SpriteKind::Coal), middle * 0.25),
|
||||
(Some(SpriteKind::Cobalt), middle * 0.1),
|
||||
(Some(SpriteKind::Silver), middle * 0.05),
|
||||
(None, 10.0),
|
||||
]
|
||||
.choose_weighted(rng, |(_, w)| *w)
|
||||
.ok()
|
||||
.and_then(|s| s.0)
|
||||
} else {
|
||||
try_spawn_entity = true;
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
{
|
||||
Block::air(sprite)
|
||||
} else if let Some(sprite) = (z == ceiling - 1 && !void_above)
|
||||
.then(|| {
|
||||
if rand.chance(wpos2d.with_z(3), biome.mushroom * 0.01) {
|
||||
Some(
|
||||
*[
|
||||
SpriteKind::CavernMycelBlue,
|
||||
SpriteKind::CeilingMushroom,
|
||||
SpriteKind::Orb,
|
||||
]
|
||||
.choose(rng)
|
||||
.unwrap(),
|
||||
)
|
||||
} else if rand.chance(wpos2d.with_z(4), biome.leafy * 0.015) {
|
||||
[
|
||||
(SpriteKind::Liana, 1.0),
|
||||
(SpriteKind::Orb, 0.35),
|
||||
(SpriteKind::CrystalHigh, 0.1),
|
||||
]
|
||||
.choose_weighted(rng, |(_, w)| *w)
|
||||
.ok()
|
||||
.map(|s| s.0)
|
||||
} else if rand.chance(wpos2d.with_z(5), 0.0075) {
|
||||
Some(*[SpriteKind::CrystalHigh].choose(rng).unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
{
|
||||
Block::air(sprite)
|
||||
} else {
|
||||
get_mushroom(wpos, rng).unwrap_or(Block::air(SpriteKind::Empty))
|
||||
}
|
||||
});
|
||||
|
||||
if try_spawn_entity {
|
||||
apply_entity_spawns(canvas, wpos, &biome, rng);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_entity_spawns<R: Rng>(canvas: &mut Canvas, wpos: Vec3<i32>, biome: &Biome, rng: &mut R) {
|
||||
if RandomField::new(canvas.info().index().seed).chance(wpos, 0.05) {
|
||||
if let Some(entity_asset) = [
|
||||
// Mushroom biome
|
||||
(
|
||||
Some("common.entity.wild.peaceful.truffler"),
|
||||
(biome.mushroom + 0.02) * 0.35,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.peaceful.fungome"),
|
||||
(biome.mushroom + 0.02) * 0.5,
|
||||
),
|
||||
// Leafy biome
|
||||
(
|
||||
Some("common.entity.wild.peaceful.holladon"),
|
||||
(biome.leafy + 0.05) * 0.5,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.peaceful.turtle"),
|
||||
(biome.leafy + 0.05) * 0.5,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.peaceful.tortoise"),
|
||||
(biome.leafy + 0.05) * 0.35,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.peaceful.axolotl"),
|
||||
(biome.leafy + 0.05) * 0.5,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.maneater"),
|
||||
(biome.leafy + 0.05) * 0.1,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.batfox"),
|
||||
(biome.leafy.max(biome.barren) + 0.3) * 0.35,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.rocksnapper"),
|
||||
(biome.leafy.max(biome.barren) + 0.1) * 0.1,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.cave_salamander"),
|
||||
(biome.leafy + 0.0) * 0.3,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.asp"),
|
||||
(biome.leafy + 0.1) * 0.25,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.swamp_troll"),
|
||||
(biome.leafy + 0.0) * 0.1,
|
||||
),
|
||||
// Dusty biome
|
||||
(
|
||||
Some("common.entity.wild.aggressive.dodarock"),
|
||||
(biome.dusty.max(biome.barren) + 0.05) * 0.35,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.cave_spider"),
|
||||
(biome.dusty + 0.0) * 0.4,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.cave_troll"),
|
||||
(biome.dusty + 0.1) * 0.05,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.antlion"),
|
||||
(biome.dusty.max(biome.barren) + 0.1) * 0.05,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.peaceful.rat"),
|
||||
(biome.dusty + 0.1) * 0.3,
|
||||
),
|
||||
// Icy biome
|
||||
(
|
||||
Some("common.entity.wild.aggressive.blue_oni"),
|
||||
(biome.icy + 0.0) * 0.03,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.icedrake"),
|
||||
(biome.icy + 0.0) * 0.1,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.wendigo"),
|
||||
(biome.icy.min(biome.depth) + 0.0) * 0.02,
|
||||
),
|
||||
// Lava biome
|
||||
(
|
||||
Some("common.entity.wild.aggressive.lavadrake"),
|
||||
(biome.fire + 0.0) * 0.5,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.basilisk"),
|
||||
(biome.fire + 0.1) * 0.1,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.peaceful.crawler_molten"),
|
||||
(biome.fire + 0.0) * 0.75,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.red_oni"),
|
||||
(biome.fire + 0.0) * 0.05,
|
||||
),
|
||||
// With depth
|
||||
(
|
||||
Some("common.entity.wild.aggressive.black_widow"),
|
||||
(biome.depth + 0.0) * 0.02,
|
||||
),
|
||||
(
|
||||
Some("common.entity.wild.aggressive.ogre"),
|
||||
(biome.depth + 0.0) * 0.02,
|
||||
),
|
||||
(None, 100.0),
|
||||
]
|
||||
.choose_weighted(rng, |(_, w)| *w)
|
||||
.ok()
|
||||
.and_then(|s| s.0)
|
||||
{
|
||||
canvas
|
||||
.spawn(EntityInfo::at(wpos.map(|e| e as f32)).with_asset_expect(entity_asset, rng));
|
||||
}
|
||||
}
|
||||
|
||||
// Occasionally place down a waypoint
|
||||
if RandomField::new(canvas.info().index().seed).chance(wpos, 0.000005) {
|
||||
canvas.spawn(EntityInfo::at(wpos.map(|e| e as f32)).into_waypoint());
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
pub mod cave;
|
||||
pub mod rock;
|
||||
pub mod scatter;
|
||||
pub mod shrub;
|
||||
@ -6,8 +7,8 @@ pub mod tree;
|
||||
pub mod wildlife;
|
||||
|
||||
pub use self::{
|
||||
rock::apply_rocks_to, scatter::apply_scatter_to, shrub::apply_shrubs_to, spot::apply_spots_to,
|
||||
tree::apply_trees_to,
|
||||
cave::apply_caves_to as apply_caves2_to, rock::apply_rocks_to, scatter::apply_scatter_to,
|
||||
shrub::apply_shrubs_to, spot::apply_spots_to, tree::apply_trees_to,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -5,7 +5,7 @@ use rand::prelude::*;
|
||||
use std::f32;
|
||||
use vek::*;
|
||||
|
||||
fn close(x: f32, tgt: f32, falloff: f32) -> f32 {
|
||||
pub fn close(x: f32, tgt: f32, falloff: f32) -> f32 {
|
||||
(1.0 - (x - tgt).abs() / falloff).max(0.0).powf(0.125)
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
all::ForestKind,
|
||||
layer::cave::tunnel_bounds_at,
|
||||
util::{gen_cache::StructureGenCache, seed_expan, Sampler, StructureGen2d, UnitChooser},
|
||||
Canvas,
|
||||
};
|
||||
@ -44,6 +45,8 @@ pub fn apply_shrubs_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) {
|
||||
&& col.alt > col.water_level
|
||||
&& col.spawn_rate > 0.9
|
||||
&& col.path.map_or(true, |(d, _, _, _)| d > 6.0)
|
||||
&& !tunnel_bounds_at(wpos, &info, &info.land())
|
||||
.any(|(_, z_range, _, _)| z_range.contains(&(col.alt as i32 - 1)))
|
||||
{
|
||||
let kind = *info
|
||||
.chunks()
|
||||
|
@ -2,8 +2,9 @@ use crate::{
|
||||
all::*,
|
||||
block::block_from_structure,
|
||||
column::ColumnGen,
|
||||
layer::cave::tunnel_bounds_at,
|
||||
util::{gen_cache::StructureGenCache, RandomPerm, Sampler, UnitChooser},
|
||||
Canvas, ColumnSample,
|
||||
Canvas, CanvasInfo, ColumnSample,
|
||||
};
|
||||
use common::{
|
||||
assets::AssetHandle,
|
||||
@ -34,11 +35,20 @@ static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7);
|
||||
static QUIRKY_RAND: RandomPerm = RandomPerm::new(0xA634460F);
|
||||
|
||||
// Ensure that it's valid to place a tree here
|
||||
pub fn tree_valid_at(col: &ColumnSample, seed: u32) -> bool {
|
||||
pub fn tree_valid_at(
|
||||
wpos: Vec2<i32>,
|
||||
col: &ColumnSample,
|
||||
info: Option<CanvasInfo<'_>>,
|
||||
seed: u32,
|
||||
) -> bool {
|
||||
if col.alt < col.water_level
|
||||
|| col.spawn_rate < 0.9
|
||||
|| col.water_dist.map(|d| d < 8.0).unwrap_or(false)
|
||||
|| col.path.map(|(d, _, _, _)| d < 12.0).unwrap_or(false)
|
||||
|| info.map_or(false, |info| {
|
||||
tunnel_bounds_at(wpos, &info, &info.land())
|
||||
.any(|(_, z_range, _, _)| z_range.contains(&(col.alt as i32 - 2)))
|
||||
})
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -85,7 +95,7 @@ pub fn apply_trees_to(
|
||||
|
||||
let col = ColumnGen::new(info.chunks()).get((wpos, info.index(), calendar))?;
|
||||
|
||||
if !tree_valid_at(&col, seed) {
|
||||
if !tree_valid_at(wpos, &col, Some(info), seed) {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,8 @@
|
||||
bool_to_option,
|
||||
label_break_value,
|
||||
option_zip,
|
||||
arbitrary_enum_discriminant
|
||||
arbitrary_enum_discriminant,
|
||||
let_else
|
||||
)]
|
||||
|
||||
mod all;
|
||||
@ -180,6 +181,14 @@ impl World {
|
||||
wpos: pos,
|
||||
}),
|
||||
)
|
||||
.chain(layer::cave::surface_entrances(&Land::from_sim(self.sim()))
|
||||
.enumerate()
|
||||
.map(|(i, wpos)| world_msg::SiteInfo {
|
||||
id: 65536 + i as u64, // Generate a fake ID, TODO: don't do this
|
||||
name: None,
|
||||
kind: world_msg::SiteKind::Cave,
|
||||
wpos,
|
||||
}))
|
||||
.collect(),
|
||||
..self.sim.get_map(index, self.sim().calendar.as_ref())
|
||||
}
|
||||
@ -369,6 +378,7 @@ impl World {
|
||||
if index.features.caves {
|
||||
layer::apply_caves_to(&mut canvas, &mut dynamic_rng);
|
||||
}
|
||||
layer::apply_caves2_to(&mut canvas, &mut dynamic_rng);
|
||||
if index.features.rocks {
|
||||
layer::apply_rocks_to(&mut canvas, &mut dynamic_rng);
|
||||
}
|
||||
@ -482,7 +492,7 @@ impl World {
|
||||
.filter_map(|attr| {
|
||||
ColumnGen::new(self.sim())
|
||||
.get((attr.pos, index, self.sim().calendar.as_ref()))
|
||||
.filter(|col| layer::tree::tree_valid_at(col, attr.seed))
|
||||
.filter(|col| layer::tree::tree_valid_at(attr.pos, col, None, attr.seed))
|
||||
.zip(Some(attr))
|
||||
})
|
||||
.filter_map(|(col, tree)| {
|
||||
|
Loading…
Reference in New Issue
Block a user