mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/lod-objects' into 'master'
LoD Objects (inc. Trees) See merge request veloren/veloren!3367
This commit is contained in:
commit
e02f8aee65
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -7,6 +7,8 @@
|
|||||||
*.ogg filter=lfs diff=lfs merge=lfs -text
|
*.ogg filter=lfs diff=lfs merge=lfs -text
|
||||||
*.ico filter=lfs diff=lfs merge=lfs -text
|
*.ico filter=lfs diff=lfs merge=lfs -text
|
||||||
*.tar filter=lfs diff=lfs merge=lfs -text
|
*.tar filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.obj filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.blend filter=lfs diff=lfs merge=lfs -text
|
||||||
assets/world/map/*.bin filter=lfs diff=lfs merge=lfs -text
|
assets/world/map/*.bin filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
||||||
*.ron gitlab-language=rust
|
*.ron gitlab-language=rust
|
||||||
|
@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Waypoints saved between sessions and shared with group members.
|
- Waypoints saved between sessions and shared with group members.
|
||||||
- New rocks
|
- New rocks
|
||||||
- Weapon trails
|
- Weapon trails
|
||||||
@ -19,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- NPCs now have rudimentary personalities
|
- NPCs now have rudimentary personalities
|
||||||
- Added Belarusian translation
|
- Added Belarusian translation
|
||||||
- Add FOV check for agents scanning for targets they are hostile to
|
- Add FOV check for agents scanning for targets they are hostile to
|
||||||
|
- Implemented an LoD system for objects, making trees visible far beyond the view distance
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -6560,6 +6560,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"tracing",
|
"tracing",
|
||||||
"walkdir 2.3.2",
|
"walkdir 2.3.2",
|
||||||
|
"wavefront",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -7340,6 +7341,15 @@ dependencies = [
|
|||||||
"wast",
|
"wast",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wavefront"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "249b7e6cd5bd1cc78a61d0475e5790c98bebabf2dc644a94a51ad58b39298652"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.9.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-client"
|
name = "wayland-client"
|
||||||
version = "0.28.6"
|
version = "0.28.6"
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
"hud.settings.reset_gameplay": "Reset to Defaults",
|
"hud.settings.reset_gameplay": "Reset to Defaults",
|
||||||
|
|
||||||
"hud.settings.view_distance": "View Distance",
|
"hud.settings.view_distance": "View Distance",
|
||||||
|
"hud.settings.lod_distance": "LoD Distance",
|
||||||
"hud.settings.sprites_view_distance": "Sprites View Distance",
|
"hud.settings.sprites_view_distance": "Sprites View Distance",
|
||||||
"hud.settings.figures_view_distance": "Entities View Distance",
|
"hud.settings.figures_view_distance": "Entities View Distance",
|
||||||
"hud.settings.maximum_fps": "Maximum FPS",
|
"hud.settings.maximum_fps": "Maximum FPS",
|
||||||
|
BIN
assets/voxygen/lod/giant_tree.obj
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/lod/giant_tree.obj
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/lod/house.obj
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/lod/house.obj
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/lod/oak.obj
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/lod/oak.obj
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/lod/pine.obj
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/lod/pine.obj
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -188,7 +188,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vec3 reflect_color = get_sky_color(/*reflect_ray_dir*/ray_dir, time_of_day.x, f_pos, vec3(-100000), 0.125, true);
|
vec3 reflect_color = get_sky_color(/*reflect_ray_dir*/ray_dir, time_of_day.x, f_pos, vec3(-100000), 0.125, true);
|
||||||
reflect_color = get_cloud_color(reflect_color, ray_dir, f_pos.xyz, time_of_day.x, 100000.0, 0.2);
|
reflect_color = get_cloud_color(reflect_color, ray_dir, f_pos.xyz, time_of_day.x, 100000.0, 0.1);
|
||||||
reflect_color *= f_light;
|
reflect_color *= f_light;
|
||||||
|
|
||||||
// Prevent the sky affecting light when underground
|
// Prevent the sky affecting light when underground
|
||||||
|
@ -54,7 +54,16 @@ void main() {
|
|||||||
// f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0));
|
// f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0));
|
||||||
// f_pos.z -= min(32.0, 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0));
|
// f_pos.z -= min(32.0, 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0));
|
||||||
|
|
||||||
|
// Terrain 'pop-in' effect
|
||||||
|
#ifndef EXPERIMENTAL_BAREMINIMUM
|
||||||
|
#ifndef EXPERIMENTAL_NOTERRAINPOP
|
||||||
f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0));
|
f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0));
|
||||||
|
// f_pos.z -= min(32.0, 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0));
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
float pull_down = pow(distance(focus_pos.xy, f_pos.xy) / (view_distance.x * 0.95), 20.0) * 0.7;
|
||||||
|
f_pos.z -= pull_down;
|
||||||
|
|
||||||
#ifdef EXPERIMENTAL_CURVEDWORLD
|
#ifdef EXPERIMENTAL_CURVEDWORLD
|
||||||
f_pos.z -= pow(distance(f_pos.xy + focus_off.xy, focus_pos.xy + focus_off.xy) * 0.05, 2);
|
f_pos.z -= pow(distance(f_pos.xy + focus_off.xy, focus_pos.xy + focus_off.xy) * 0.05, 2);
|
||||||
|
@ -324,7 +324,7 @@ vec3 lod_norm(vec2 f_pos/*vec3 pos*/) {
|
|||||||
norm.xy += vec2(
|
norm.xy += vec2(
|
||||||
textureLod(sampler2D(t_noise, s_noise), wpos / 100, 0).x - 0.5,
|
textureLod(sampler2D(t_noise, s_noise), wpos / 100, 0).x - 0.5,
|
||||||
textureLod(sampler2D(t_noise, s_noise), wpos / 100 + 0.5, 0).x - 0.5
|
textureLod(sampler2D(t_noise, s_noise), wpos / 100 + 0.5, 0).x - 0.5
|
||||||
) * 0.15 / pow(norm.z + 0.1, 3);
|
) * 0.25 / pow(norm.z + 0.1, 3);
|
||||||
norm = normalize(norm);
|
norm = normalize(norm);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -373,6 +373,7 @@ vec3 lod_col(vec2 pos) {
|
|||||||
|
|
||||||
vec3 col = textureBicubic(t_map, s_map, pos_to_tex(pos)).rgb;
|
vec3 col = textureBicubic(t_map, s_map, pos_to_tex(pos)).rgb;
|
||||||
|
|
||||||
|
/*
|
||||||
#ifdef EXPERIMENTAL_PROCEDURALLODDETAIL
|
#ifdef EXPERIMENTAL_PROCEDURALLODDETAIL
|
||||||
col *= pow(vec3(
|
col *= pow(vec3(
|
||||||
textureLod(sampler2D(t_noise, s_noise), wpos / 40, 0).x - 0.5,
|
textureLod(sampler2D(t_noise, s_noise), wpos / 40, 0).x - 0.5,
|
||||||
@ -380,6 +381,7 @@ vec3 lod_col(vec2 pos) {
|
|||||||
textureLod(sampler2D(t_noise, s_noise), wpos / 45 + 0.75, 0).x - 0.5
|
textureLod(sampler2D(t_noise, s_noise), wpos / 45 + 0.75, 0).x - 0.5
|
||||||
) + 1.0, vec3(0.5));
|
) + 1.0, vec3(0.5));
|
||||||
#endif
|
#endif
|
||||||
|
*/
|
||||||
|
|
||||||
return col;
|
return col;
|
||||||
}
|
}
|
||||||
|
126
assets/voxygen/shaders/lod-object-frag.glsl
Normal file
126
assets/voxygen/shaders/lod-object-frag.glsl
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
#version 420 core
|
||||||
|
|
||||||
|
#include <constants.glsl>
|
||||||
|
|
||||||
|
#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
|
||||||
|
|
||||||
|
#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY
|
||||||
|
|
||||||
|
#if (FLUID_MODE == FLUID_MODE_CHEAP)
|
||||||
|
#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE
|
||||||
|
#elif (FLUID_MODE == FLUID_MODE_SHINY)
|
||||||
|
#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_RADIANCE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET
|
||||||
|
|
||||||
|
#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN
|
||||||
|
|
||||||
|
#include <globals.glsl>
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 f_pos;
|
||||||
|
layout(location = 1) in vec3 f_norm;
|
||||||
|
layout(location = 2) in vec4 f_col;
|
||||||
|
layout(location = 3) in vec3 model_pos;
|
||||||
|
layout(location = 4) in float snow_cover;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 tgt_color;
|
||||||
|
|
||||||
|
#include <sky.glsl>
|
||||||
|
#include <light.glsl>
|
||||||
|
#include <lod.glsl>
|
||||||
|
|
||||||
|
const float FADE_DIST = 32.0;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
#ifdef EXPERIMENTAL_BAREMINIMUM
|
||||||
|
tgt_color = vec4(simple_lighting(f_pos.xyz, f_col.rgb, 1.0), 1);
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
vec3 cam_to_frag = normalize(f_pos - cam_pos.xyz);
|
||||||
|
vec3 view_dir = -cam_to_frag;
|
||||||
|
|
||||||
|
#if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP || FLUID_MODE == FLUID_MODE_SHINY)
|
||||||
|
float f_alt = alt_at(f_pos.xy);
|
||||||
|
#elif (SHADOW_MODE == SHADOW_MODE_NONE || FLUID_MODE == FLUID_MODE_CHEAP)
|
||||||
|
float f_alt = f_pos.z;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP)
|
||||||
|
vec4 f_shadow = textureBicubic(t_horizon, s_horizon, pos_to_tex(f_pos.xy));
|
||||||
|
float sun_shade_frac = horizon_at2(f_shadow, f_alt, f_pos, sun_dir);
|
||||||
|
#elif (SHADOW_MODE == SHADOW_MODE_NONE)
|
||||||
|
float sun_shade_frac = 1.0;
|
||||||
|
#endif
|
||||||
|
float moon_shade_frac = 1.0;
|
||||||
|
|
||||||
|
DirectionalLight sun_info = get_sun_info(sun_dir, sun_shade_frac, f_pos);
|
||||||
|
DirectionalLight moon_info = get_moon_info(moon_dir, moon_shade_frac);
|
||||||
|
|
||||||
|
vec3 surf_color = f_col.rgb;
|
||||||
|
float alpha = 1.0;
|
||||||
|
const float n2 = 1.5;
|
||||||
|
const float R_s2s0 = pow((1.0 - n2) / (1.0 + n2), 2);
|
||||||
|
const float R_s1s0 = pow((1.3325 - n2) / (1.3325 + n2), 2);
|
||||||
|
const float R_s2s1 = pow((1.0 - 1.3325) / (1.0 + 1.3325), 2);
|
||||||
|
const float R_s1s2 = pow((1.3325 - 1.0) / (1.3325 + 1.0), 2);
|
||||||
|
float R_s = (f_pos.z < f_alt) ? mix(R_s2s1 * R_s1s0, R_s1s0, medium.x) : mix(R_s2s0, R_s1s2 * R_s2s0, medium.x);
|
||||||
|
|
||||||
|
vec3 k_a = vec3(1.0);
|
||||||
|
vec3 k_d = vec3(1.0);
|
||||||
|
vec3 k_s = vec3(R_s);
|
||||||
|
|
||||||
|
vec3 my_norm = vec3(f_norm.xy, abs(f_norm.z));
|
||||||
|
vec3 voxel_norm;
|
||||||
|
float my_alt = f_pos.z + focus_off.z;
|
||||||
|
float f_ao = 1.0;
|
||||||
|
const float VOXELIZE_DIST = 2000;
|
||||||
|
float voxelize_factor = clamp(1.0 - (distance(focus_pos.xy, f_pos.xy) - view_distance.x) / VOXELIZE_DIST, 0, 0.65);
|
||||||
|
vec3 cam_dir = normalize(cam_pos.xyz - f_pos.xyz);
|
||||||
|
vec3 side_norm = normalize(vec3(my_norm.xy, 0));
|
||||||
|
vec3 top_norm = vec3(0, 0, 1);
|
||||||
|
#ifdef EXPERIMENTAL_NOLODVOXELS
|
||||||
|
f_ao = 1.0;
|
||||||
|
voxel_norm = normalize(mix(side_norm, top_norm, cam_dir.z));
|
||||||
|
#else
|
||||||
|
float side_factor = 1.0 - my_norm.z;
|
||||||
|
// min(dot(vec3(0, -sign(cam_dir.y), 0), -cam_dir), dot(vec3(-sign(cam_dir.x), 0, 0), -cam_dir))
|
||||||
|
if (max(abs(my_norm.x), abs(my_norm.y)) < 0.01 || fract(my_alt) * clamp(dot(normalize(vec3(cam_dir.xy, 0)), side_norm), 0, 1) < cam_dir.z / my_norm.z) {
|
||||||
|
f_ao *= mix(1.0, clamp(fract(my_alt) / length(my_norm.xy) + clamp(dot(side_norm, -cam_dir), 0, 1), 0, 1), voxelize_factor);
|
||||||
|
voxel_norm = top_norm;
|
||||||
|
} else {
|
||||||
|
f_ao *= mix(1.0, clamp(pow(fract(my_alt), 0.5), 0, 1), voxelize_factor);
|
||||||
|
|
||||||
|
if (fract(f_pos.x) * abs(my_norm.y / cam_dir.x) < fract(f_pos.y) * abs(my_norm.x / cam_dir.y)) {
|
||||||
|
voxel_norm = vec3(sign(cam_dir.x), 0, 0);
|
||||||
|
} else {
|
||||||
|
voxel_norm = vec3(0, sign(cam_dir.y), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f_ao = min(f_ao, max(f_norm.z * 0.5 + 0.5, 0.0));
|
||||||
|
voxel_norm = mix(my_norm, voxel_norm == vec3(0.0) ? f_norm : voxel_norm, voxelize_factor);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
vec3 emitted_light, reflected_light;
|
||||||
|
|
||||||
|
// To account for prior saturation.
|
||||||
|
float max_light = 0.0;
|
||||||
|
|
||||||
|
vec3 cam_attenuation = vec3(1);
|
||||||
|
float fluid_alt = max(f_pos.z + 1, floor(f_alt + 1));
|
||||||
|
vec3 mu = medium.x == MEDIUM_WATER ? MU_WATER : vec3(0.0);
|
||||||
|
|
||||||
|
max_light += get_sun_diffuse2(sun_info, moon_info, voxel_norm, view_dir, f_pos, mu, cam_attenuation, fluid_alt, k_a, k_d, k_s, alpha, voxel_norm, 1.0, emitted_light, reflected_light);
|
||||||
|
|
||||||
|
emitted_light *= f_ao;
|
||||||
|
reflected_light *= f_ao;
|
||||||
|
|
||||||
|
vec3 side_color = mix(surf_color, vec3(0.5, 0.6, 1.0), snow_cover);
|
||||||
|
vec3 top_color = mix(surf_color, surf_color * 0.3, 0.5 + snow_cover * 0.5);
|
||||||
|
surf_color = mix(side_color, top_color, pow(fract(model_pos.z * 0.1), 2.0));
|
||||||
|
|
||||||
|
surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light);
|
||||||
|
|
||||||
|
tgt_color = vec4(surf_color, 1.0);
|
||||||
|
}
|
63
assets/voxygen/shaders/lod-object-vert.glsl
Normal file
63
assets/voxygen/shaders/lod-object-vert.glsl
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#version 420 core
|
||||||
|
|
||||||
|
#include <constants.glsl>
|
||||||
|
|
||||||
|
#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
|
||||||
|
|
||||||
|
#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY
|
||||||
|
|
||||||
|
#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE
|
||||||
|
|
||||||
|
#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET
|
||||||
|
|
||||||
|
#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN
|
||||||
|
|
||||||
|
#include <globals.glsl>
|
||||||
|
#include <srgb.glsl>
|
||||||
|
#include <random.glsl>
|
||||||
|
#include <lod.glsl>
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 v_pos;
|
||||||
|
layout(location = 1) in vec3 v_norm;
|
||||||
|
layout(location = 2) in vec3 v_col;
|
||||||
|
layout(location = 3) in vec3 inst_pos;
|
||||||
|
layout(location = 4) in uvec3 inst_col;
|
||||||
|
layout(location = 5) in uint inst_flags;
|
||||||
|
|
||||||
|
const uint FLAG_SNOW_COVERED = 1;
|
||||||
|
|
||||||
|
layout(location = 0) out vec3 f_pos;
|
||||||
|
layout(location = 1) out vec3 f_norm;
|
||||||
|
layout(location = 2) out vec4 f_col;
|
||||||
|
layout(location = 3) out vec3 model_pos;
|
||||||
|
layout(location = 4) out float snow_cover;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 obj_pos = inst_pos - focus_off.xyz;
|
||||||
|
f_pos = obj_pos + v_pos;
|
||||||
|
model_pos = v_pos;
|
||||||
|
|
||||||
|
float pull_down = 1.0 / pow(distance(focus_pos.xy, obj_pos.xy) / (view_distance.x * 0.95), 150.0);
|
||||||
|
#ifndef EXPERIMENTAL_NOTERRAINPOP
|
||||||
|
f_pos.z -= pull_down;
|
||||||
|
#else
|
||||||
|
f_pos.z -= step(0.1, pull_down) * 10000.0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef EXPERIMENTAL_CURVEDWORLD
|
||||||
|
f_pos.z -= pow(distance(f_pos.xy + focus_off.xy, focus_pos.xy + focus_off.xy) * 0.05, 2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
f_norm = v_norm;
|
||||||
|
f_col = vec4(vec3(inst_col) * (1.0 / 255.0) * v_col * (hash(inst_pos.xyxy) * 0.35 + 0.65), 1.0);
|
||||||
|
|
||||||
|
if ((inst_flags & FLAG_SNOW_COVERED) > 0u) {
|
||||||
|
snow_cover = 1.0;
|
||||||
|
} else {
|
||||||
|
snow_cover = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_Position =
|
||||||
|
all_mat *
|
||||||
|
vec4(f_pos, 1);
|
||||||
|
}
|
@ -333,12 +333,15 @@ void main() {
|
|||||||
hit_xy ? vec3(0.0, 0.0, sides.z) : vec3(0.0, 0.0, 0.0);
|
hit_xy ? vec3(0.0, 0.0, sides.z) : vec3(0.0, 0.0, 0.0);
|
||||||
*/
|
*/
|
||||||
vec3 voxel_norm;
|
vec3 voxel_norm;
|
||||||
|
|
||||||
const float VOXELIZE_DIST = 2000;
|
const float VOXELIZE_DIST = 2000;
|
||||||
float voxelize_factor = clamp(1.0 - (distance(focus_pos.xy, f_pos.xy) - view_distance.x) / VOXELIZE_DIST, 0, 1);
|
float voxelize_factor = clamp(1.0 - (distance(focus_pos.xy, f_pos.xy) - view_distance.x) / VOXELIZE_DIST, 0, 1);
|
||||||
vec3 cam_dir = normalize(cam_pos.xyz - f_pos.xyz);
|
vec3 cam_dir = normalize(cam_pos.xyz - f_pos.xyz);
|
||||||
vec3 side_norm = normalize(vec3(my_norm.xy, 0));
|
vec3 side_norm = normalize(vec3(my_norm.xy, 0));
|
||||||
vec3 top_norm = vec3(0, 0, 1);
|
vec3 top_norm = vec3(0, 0, 1);
|
||||||
|
#ifdef EXPERIMENTAL_NOLODVOXELS
|
||||||
|
f_ao = 1.0;
|
||||||
|
voxel_norm = normalize(mix(side_norm, top_norm, max(cam_dir.z, 0.0)));
|
||||||
|
#else
|
||||||
float side_factor = 1.0 - my_norm.z;
|
float side_factor = 1.0 - my_norm.z;
|
||||||
// min(dot(vec3(0, -sign(cam_dir.y), 0), -cam_dir), dot(vec3(-sign(cam_dir.x), 0, 0), -cam_dir))
|
// min(dot(vec3(0, -sign(cam_dir.y), 0), -cam_dir), dot(vec3(-sign(cam_dir.x), 0, 0), -cam_dir))
|
||||||
if (max(abs(my_norm.x), abs(my_norm.y)) < 0.01 || fract(my_alt) * clamp(dot(normalize(vec3(cam_dir.xy, 0)), side_norm), 0, 1) < cam_dir.z / my_norm.z) {
|
if (max(abs(my_norm.x), abs(my_norm.y)) < 0.01 || fract(my_alt) * clamp(dot(normalize(vec3(cam_dir.xy, 0)), side_norm), 0, 1) < cam_dir.z / my_norm.z) {
|
||||||
@ -353,6 +356,7 @@ void main() {
|
|||||||
voxel_norm = vec3(0, sign(cam_dir.y), 0);
|
voxel_norm = vec3(0, sign(cam_dir.y), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// vec3 f_ao_view = max(vec3(dot(f_orig_view_dir.yz, sides.yz), dot(f_orig_view_dir.xz, sides.xz), dot(f_orig_view_dir.xy, sides.xy)), 0.0);
|
// vec3 f_ao_view = max(vec3(dot(f_orig_view_dir.yz, sides.yz), dot(f_orig_view_dir.xz, sides.xz), dot(f_orig_view_dir.xy, sides.xy)), 0.0);
|
||||||
// delta_sides *= sqrt(1.0 - f_ao_view * f_ao_view);
|
// delta_sides *= sqrt(1.0 - f_ao_view * f_ao_view);
|
||||||
|
@ -81,9 +81,11 @@ void main() {
|
|||||||
|
|
||||||
// Terrain 'pop-in' effect
|
// Terrain 'pop-in' effect
|
||||||
#ifndef EXPERIMENTAL_BAREMINIMUM
|
#ifndef EXPERIMENTAL_BAREMINIMUM
|
||||||
|
#ifndef EXPERIMENTAL_NOTERRAINPOP
|
||||||
v_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0));
|
v_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0));
|
||||||
// f_pos.z -= min(32.0, 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0));
|
// f_pos.z -= min(32.0, 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0));
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef EXPERIMENTAL_CURVEDWORLD
|
#ifdef EXPERIMENTAL_CURVEDWORLD
|
||||||
v_pos.z -= pow(distance(v_pos.xy + focus_off.xy, focus_pos.xy + focus_off.xy) * 0.05, 2);
|
v_pos.z -= pow(distance(v_pos.xy + focus_off.xy, focus_pos.xy + focus_off.xy) * 0.05, 2);
|
||||||
|
@ -35,10 +35,12 @@ use common::{
|
|||||||
event::{EventBus, LocalEvent},
|
event::{EventBus, LocalEvent},
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
link::Is,
|
link::Is,
|
||||||
|
lod,
|
||||||
mounting::Rider,
|
mounting::Rider,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
recipe::RecipeBook,
|
recipe::RecipeBook,
|
||||||
resources::{PlayerEntity, TimeOfDay},
|
resources::{PlayerEntity, TimeOfDay},
|
||||||
|
spiral::Spiral2d,
|
||||||
terrain::{
|
terrain::{
|
||||||
block::Block, map::MapConfig, neighbors, BiomeKind, SitesKind, SpriteKind, TerrainChunk,
|
block::Block, map::MapConfig, neighbors, BiomeKind, SitesKind, SpriteKind, TerrainChunk,
|
||||||
TerrainChunkSize,
|
TerrainChunkSize,
|
||||||
@ -171,10 +173,12 @@ pub struct Client {
|
|||||||
pub chat_mode: ChatMode,
|
pub chat_mode: ChatMode,
|
||||||
recipe_book: RecipeBook,
|
recipe_book: RecipeBook,
|
||||||
available_recipes: HashMap<String, Option<SpriteKind>>,
|
available_recipes: HashMap<String, Option<SpriteKind>>,
|
||||||
|
lod_zones: HashMap<Vec2<i32>, lod::Zone>,
|
||||||
|
lod_last_requested: Option<Instant>,
|
||||||
|
|
||||||
max_group_size: u32,
|
max_group_size: u32,
|
||||||
// Client has received an invite (inviter uid, time out instant)
|
// Client has received an invite (inviter uid, time out instant)
|
||||||
invite: Option<(Uid, std::time::Instant, std::time::Duration, InviteKind)>,
|
invite: Option<(Uid, Instant, Duration, InviteKind)>,
|
||||||
group_leader: Option<Uid>,
|
group_leader: Option<Uid>,
|
||||||
// Note: potentially representable as a client only component
|
// Note: potentially representable as a client only component
|
||||||
group_members: HashMap<Uid, group::Role>,
|
group_members: HashMap<Uid, group::Role>,
|
||||||
@ -202,6 +206,7 @@ pub struct Client {
|
|||||||
state: State,
|
state: State,
|
||||||
|
|
||||||
view_distance: Option<u32>,
|
view_distance: Option<u32>,
|
||||||
|
lod_distance: f32,
|
||||||
// TODO: move into voxygen
|
// TODO: move into voxygen
|
||||||
loaded_distance: f32,
|
loaded_distance: f32,
|
||||||
|
|
||||||
@ -624,6 +629,9 @@ impl Client {
|
|||||||
available_recipes: HashMap::default(),
|
available_recipes: HashMap::default(),
|
||||||
chat_mode: ChatMode::default(),
|
chat_mode: ChatMode::default(),
|
||||||
|
|
||||||
|
lod_zones: HashMap::new(),
|
||||||
|
lod_last_requested: None,
|
||||||
|
|
||||||
max_group_size,
|
max_group_size,
|
||||||
invite: None,
|
invite: None,
|
||||||
group_leader: None,
|
group_leader: None,
|
||||||
@ -650,6 +658,7 @@ impl Client {
|
|||||||
tick: 0,
|
tick: 0,
|
||||||
state,
|
state,
|
||||||
view_distance: None,
|
view_distance: None,
|
||||||
|
lod_distance: 4.0,
|
||||||
loaded_distance: 0.0,
|
loaded_distance: 0.0,
|
||||||
|
|
||||||
pending_chunks: HashMap::new(),
|
pending_chunks: HashMap::new(),
|
||||||
@ -769,7 +778,8 @@ impl Client {
|
|||||||
&mut self.in_game_stream
|
&mut self.in_game_stream
|
||||||
},
|
},
|
||||||
//Only in game, terrain
|
//Only in game, terrain
|
||||||
ClientGeneral::TerrainChunkRequest { .. } => {
|
ClientGeneral::TerrainChunkRequest { .. }
|
||||||
|
| ClientGeneral::LodZoneRequest { .. } => {
|
||||||
#[cfg(feature = "tracy")]
|
#[cfg(feature = "tracy")]
|
||||||
{
|
{
|
||||||
terrain = 1.0;
|
terrain = 1.0;
|
||||||
@ -880,6 +890,11 @@ impl Client {
|
|||||||
self.send_msg(ClientGeneral::SetViewDistance(view_distance));
|
self.send_msg(ClientGeneral::SetViewDistance(view_distance));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_lod_distance(&mut self, lod_distance: u32) {
|
||||||
|
let lod_distance = lod_distance.max(0).min(1000) as f32 / lod::ZONE_SIZE as f32;
|
||||||
|
self.lod_distance = lod_distance;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn use_slot(&mut self, slot: Slot) {
|
pub fn use_slot(&mut self, slot: Slot) {
|
||||||
self.control_action(ControlAction::InventoryAction(InventoryAction::Use(slot)))
|
self.control_action(ControlAction::InventoryAction(InventoryAction::Use(slot)))
|
||||||
}
|
}
|
||||||
@ -994,6 +1009,8 @@ impl Client {
|
|||||||
&self.available_recipes
|
&self.available_recipes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lod_zones(&self) -> &HashMap<Vec2<i32>, lod::Zone> { &self.lod_zones }
|
||||||
|
|
||||||
/// Returns whether the specified recipe can be crafted and the sprite, if
|
/// Returns whether the specified recipe can be crafted and the sprite, if
|
||||||
/// any, that is required to do so.
|
/// any, that is required to do so.
|
||||||
pub fn can_craft_recipe(&self, recipe: &str) -> (bool, Option<SpriteKind>) {
|
pub fn can_craft_recipe(&self, recipe: &str) -> (bool, Option<SpriteKind>) {
|
||||||
@ -1709,6 +1726,34 @@ impl Client {
|
|||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
self.pending_chunks
|
self.pending_chunks
|
||||||
.retain(|_, created| now.duration_since(*created) < Duration::from_secs(3));
|
.retain(|_, created| now.duration_since(*created) < Duration::from_secs(3));
|
||||||
|
|
||||||
|
// Manage LoD zones
|
||||||
|
let lod_zone = pos.0.xy().map(|e| lod::from_wpos(e as i32));
|
||||||
|
|
||||||
|
// Request LoD zones that are in range
|
||||||
|
if self
|
||||||
|
.lod_last_requested
|
||||||
|
.map_or(true, |i| i.elapsed() > Duration::from_secs(5))
|
||||||
|
{
|
||||||
|
if let Some(rpos) = Spiral2d::new()
|
||||||
|
.take((1 + self.lod_distance.ceil() as i32 * 2).pow(2) as usize)
|
||||||
|
.filter(|rpos| !self.lod_zones.contains_key(&(lod_zone + *rpos)))
|
||||||
|
.min_by_key(|rpos| rpos.magnitude_squared())
|
||||||
|
.filter(|rpos| {
|
||||||
|
rpos.map(|e| e as f32).magnitude() < (self.lod_distance - 0.5).max(0.0)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
self.send_msg_err(ClientGeneral::LodZoneRequest {
|
||||||
|
key: lod_zone + rpos,
|
||||||
|
})?;
|
||||||
|
self.lod_last_requested = Some(Instant::now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cull LoD zones out of range
|
||||||
|
self.lod_zones.retain(|p, _| {
|
||||||
|
(*p - lod_zone).map(|e| e as f32).magnitude_squared() < self.lod_distance.powi(2)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -2084,6 +2129,10 @@ impl Client {
|
|||||||
}
|
}
|
||||||
self.pending_chunks.remove(&key);
|
self.pending_chunks.remove(&key);
|
||||||
},
|
},
|
||||||
|
ServerGeneral::LodZoneUpdate { key, zone } => {
|
||||||
|
self.lod_zones.insert(key, zone);
|
||||||
|
self.lod_last_requested = None;
|
||||||
|
},
|
||||||
ServerGeneral::TerrainBlockUpdates(blocks) => {
|
ServerGeneral::TerrainBlockUpdates(blocks) => {
|
||||||
if let Some(mut blocks) = blocks.decompress() {
|
if let Some(mut blocks) = blocks.decompress() {
|
||||||
blocks.drain().for_each(|(pos, block)| {
|
blocks.drain().for_each(|(pos, block)| {
|
||||||
|
@ -10,6 +10,7 @@ lazy_static = "1.4.0"
|
|||||||
assets_manager = {version = "0.7", features = ["bincode", "ron", "json"]}
|
assets_manager = {version = "0.7", features = ["bincode", "ron", "json"]}
|
||||||
ron = { version = "0.7", default-features = false }
|
ron = { version = "0.7", default-features = false }
|
||||||
dot_vox = "4.0"
|
dot_vox = "4.0"
|
||||||
|
wavefront = "0.2" # TODO: Use vertex-colors branch when we have models that have them
|
||||||
image = { version = "0.23.12", default-features = false, features = ["png"] }
|
image = { version = "0.23.12", default-features = false, features = ["png"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|
||||||
|
@ -191,6 +191,22 @@ impl Asset for DotVoxAsset {
|
|||||||
const EXTENSION: &'static str = "vox";
|
const EXTENSION: &'static str = "vox";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ObjAsset(pub wavefront::Obj);
|
||||||
|
|
||||||
|
impl Asset for ObjAsset {
|
||||||
|
type Loader = ObjAssetLoader;
|
||||||
|
|
||||||
|
const EXTENSION: &'static str = "obj";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ObjAssetLoader;
|
||||||
|
impl Loader<ObjAsset> for ObjAssetLoader {
|
||||||
|
fn load(content: std::borrow::Cow<[u8]>, _: &str) -> Result<ObjAsset, BoxedError> {
|
||||||
|
let data = wavefront::Obj::from_reader(&*content)?;
|
||||||
|
Ok(ObjAsset(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return path to repository root by searching 10 directories back
|
/// Return path to repository root by searching 10 directories back
|
||||||
pub fn find_root() -> Option<PathBuf> {
|
pub fn find_root() -> Option<PathBuf> {
|
||||||
std::env::current_dir().map_or(None, |path| {
|
std::env::current_dir().map_or(None, |path| {
|
||||||
|
@ -83,6 +83,9 @@ pub enum ClientGeneral {
|
|||||||
TerrainChunkRequest {
|
TerrainChunkRequest {
|
||||||
key: Vec2<i32>,
|
key: Vec2<i32>,
|
||||||
},
|
},
|
||||||
|
LodZoneRequest {
|
||||||
|
key: Vec2<i32>,
|
||||||
|
},
|
||||||
//Always possible
|
//Always possible
|
||||||
ChatMsg(String),
|
ChatMsg(String),
|
||||||
Command(String, Vec<String>),
|
Command(String, Vec<String>),
|
||||||
@ -128,6 +131,7 @@ impl ClientMsg {
|
|||||||
| ClientGeneral::ExitInGame
|
| ClientGeneral::ExitInGame
|
||||||
| ClientGeneral::PlayerPhysics { .. }
|
| ClientGeneral::PlayerPhysics { .. }
|
||||||
| ClientGeneral::TerrainChunkRequest { .. }
|
| ClientGeneral::TerrainChunkRequest { .. }
|
||||||
|
| ClientGeneral::LodZoneRequest { .. }
|
||||||
| ClientGeneral::UnlockSkill(_)
|
| ClientGeneral::UnlockSkill(_)
|
||||||
| ClientGeneral::RequestSiteInfo(_)
|
| ClientGeneral::RequestSiteInfo(_)
|
||||||
| ClientGeneral::UnlockSkillGroup(_)
|
| ClientGeneral::UnlockSkillGroup(_)
|
||||||
|
@ -7,6 +7,7 @@ use common::{
|
|||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
character::{self, CharacterItem},
|
character::{self, CharacterItem},
|
||||||
comp::{self, invite::InviteKind, item::MaterialStatManifest},
|
comp::{self, invite::InviteKind, item::MaterialStatManifest},
|
||||||
|
lod,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
recipe::RecipeBook,
|
recipe::RecipeBook,
|
||||||
resources::TimeOfDay,
|
resources::TimeOfDay,
|
||||||
@ -169,6 +170,10 @@ pub enum ServerGeneral {
|
|||||||
key: Vec2<i32>,
|
key: Vec2<i32>,
|
||||||
chunk: Result<SerializedTerrainChunk, ()>,
|
chunk: Result<SerializedTerrainChunk, ()>,
|
||||||
},
|
},
|
||||||
|
LodZoneUpdate {
|
||||||
|
key: Vec2<i32>,
|
||||||
|
zone: lod::Zone,
|
||||||
|
},
|
||||||
TerrainBlockUpdates(CompressedData<HashMap<Vec3<i32>, Block>>),
|
TerrainBlockUpdates(CompressedData<HashMap<Vec3<i32>, Block>>),
|
||||||
// Always possible
|
// Always possible
|
||||||
PlayerListUpdate(PlayerListUpdate),
|
PlayerListUpdate(PlayerListUpdate),
|
||||||
@ -293,6 +298,7 @@ impl ServerMsg {
|
|||||||
| ServerGeneral::ExitInGameSuccess
|
| ServerGeneral::ExitInGameSuccess
|
||||||
| ServerGeneral::InventoryUpdate(_, _)
|
| ServerGeneral::InventoryUpdate(_, _)
|
||||||
| ServerGeneral::TerrainChunkUpdate { .. }
|
| ServerGeneral::TerrainChunkUpdate { .. }
|
||||||
|
| ServerGeneral::LodZoneUpdate { .. }
|
||||||
| ServerGeneral::TerrainBlockUpdates(_)
|
| ServerGeneral::TerrainBlockUpdates(_)
|
||||||
| ServerGeneral::SetViewDistance(_)
|
| ServerGeneral::SetViewDistance(_)
|
||||||
| ServerGeneral::Outcomes(_)
|
| ServerGeneral::Outcomes(_)
|
||||||
|
@ -49,6 +49,7 @@ pub mod figure;
|
|||||||
pub mod generation;
|
pub mod generation;
|
||||||
#[cfg(not(target_arch = "wasm32"))] pub mod grid;
|
#[cfg(not(target_arch = "wasm32"))] pub mod grid;
|
||||||
#[cfg(not(target_arch = "wasm32"))] pub mod link;
|
#[cfg(not(target_arch = "wasm32"))] pub mod link;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))] pub mod lod;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub mod lottery;
|
pub mod lottery;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
41
common/src/lod.rs
Normal file
41
common/src/lod.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
use crate::{terrain::TerrainChunkSize, vol::RectVolSize};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use strum::EnumIter;
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
// In chunks
|
||||||
|
pub const ZONE_SIZE: u32 = 32;
|
||||||
|
|
||||||
|
bitflags::bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Flags: u8 {
|
||||||
|
const SNOW_COVERED = 0b00000001;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize, EnumIter)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum ObjectKind {
|
||||||
|
Oak,
|
||||||
|
Pine,
|
||||||
|
House,
|
||||||
|
GiantTree,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Object {
|
||||||
|
pub kind: ObjectKind,
|
||||||
|
pub pos: Vec3<i16>,
|
||||||
|
pub flags: Flags,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Zone {
|
||||||
|
pub objects: Vec<Object>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_wpos(wpos: i32) -> i32 { wpos * (TerrainChunkSize::RECT_SIZE.x * ZONE_SIZE) as i32 }
|
||||||
|
|
||||||
|
pub fn from_wpos(zone_pos: i32) -> i32 {
|
||||||
|
zone_pos.div_euclid((TerrainChunkSize::RECT_SIZE.x * ZONE_SIZE) as i32)
|
||||||
|
}
|
@ -118,6 +118,7 @@ impl Client {
|
|||||||
},
|
},
|
||||||
//Ingame related, terrain
|
//Ingame related, terrain
|
||||||
ServerGeneral::TerrainChunkUpdate { .. }
|
ServerGeneral::TerrainChunkUpdate { .. }
|
||||||
|
| ServerGeneral::LodZoneUpdate { .. }
|
||||||
| ServerGeneral::TerrainBlockUpdates(_) => {
|
| ServerGeneral::TerrainBlockUpdates(_) => {
|
||||||
self.terrain_stream.lock().unwrap().send(g)
|
self.terrain_stream.lock().unwrap().send(g)
|
||||||
},
|
},
|
||||||
@ -191,6 +192,7 @@ impl Client {
|
|||||||
},
|
},
|
||||||
//Ingame related, terrain
|
//Ingame related, terrain
|
||||||
ServerGeneral::TerrainChunkUpdate { .. }
|
ServerGeneral::TerrainChunkUpdate { .. }
|
||||||
|
| ServerGeneral::LodZoneUpdate { .. }
|
||||||
| ServerGeneral::TerrainBlockUpdates(_) => {
|
| ServerGeneral::TerrainBlockUpdates(_) => {
|
||||||
PreparedMsg::new(5, &g, &self.terrain_stream_params)
|
PreparedMsg::new(5, &g, &self.terrain_stream_params)
|
||||||
},
|
},
|
||||||
|
@ -24,6 +24,7 @@ pub mod error;
|
|||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod location;
|
pub mod location;
|
||||||
|
pub mod lod;
|
||||||
pub mod login_provider;
|
pub mod login_provider;
|
||||||
pub mod metrics;
|
pub mod metrics;
|
||||||
pub mod persistence;
|
pub mod persistence;
|
||||||
@ -449,6 +450,9 @@ impl Server {
|
|||||||
// Insert the world into the ECS (todo: Maybe not an Arc?)
|
// Insert the world into the ECS (todo: Maybe not an Arc?)
|
||||||
let world = Arc::new(world);
|
let world = Arc::new(world);
|
||||||
state.ecs_mut().insert(Arc::clone(&world));
|
state.ecs_mut().insert(Arc::clone(&world));
|
||||||
|
state
|
||||||
|
.ecs_mut()
|
||||||
|
.insert(lod::Lod::from_world(&world, index.as_index_ref()));
|
||||||
state.ecs_mut().insert(index.clone());
|
state.ecs_mut().insert(index.clone());
|
||||||
|
|
||||||
// Set starting time for the server.
|
// Set starting time for the server.
|
||||||
|
33
server/src/lod.rs
Normal file
33
server/src/lod.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use common::lod;
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use vek::*;
|
||||||
|
use world::World;
|
||||||
|
|
||||||
|
static EMPTY_ZONE: lod::Zone = lod::Zone {
|
||||||
|
objects: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Lod {
|
||||||
|
pub zones: HashMap<Vec2<i32>, lod::Zone>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Lod {
|
||||||
|
pub fn from_world(world: &World, index: world::IndexRef) -> Self {
|
||||||
|
let mut zones = HashMap::new();
|
||||||
|
|
||||||
|
let zone_sz = (world.sim().get_size() + lod::ZONE_SIZE - 1) / lod::ZONE_SIZE;
|
||||||
|
|
||||||
|
for i in 0..zone_sz.x {
|
||||||
|
for j in 0..zone_sz.y {
|
||||||
|
let zone_pos = Vec2::new(i, j).map(|e| e as i32);
|
||||||
|
zones.insert(zone_pos, world.get_lod_zone(zone_pos, index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { zones }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn zone(&self, zone_pos: Vec2<i32>) -> &lod::Zone {
|
||||||
|
self.zones.get(&zone_pos).unwrap_or(&EMPTY_ZONE)
|
||||||
|
}
|
||||||
|
}
|
@ -295,6 +295,7 @@ impl Sys {
|
|||||||
| ClientGeneral::Character(_)
|
| ClientGeneral::Character(_)
|
||||||
| ClientGeneral::Spectate
|
| ClientGeneral::Spectate
|
||||||
| ClientGeneral::TerrainChunkRequest { .. }
|
| ClientGeneral::TerrainChunkRequest { .. }
|
||||||
|
| ClientGeneral::LodZoneRequest { .. }
|
||||||
| ClientGeneral::ChatMsg(_)
|
| ClientGeneral::ChatMsg(_)
|
||||||
| ClientGeneral::Command(..)
|
| ClientGeneral::Command(..)
|
||||||
| ClientGeneral::Terminate => {
|
| ClientGeneral::Terminate => {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use crate::{client::Client, metrics::NetworkRequestMetrics, presence::Presence, ChunkRequest};
|
use crate::{
|
||||||
|
client::Client, lod::Lod, metrics::NetworkRequestMetrics, presence::Presence, ChunkRequest,
|
||||||
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::Pos,
|
comp::Pos,
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
@ -20,6 +22,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
Entities<'a>,
|
Entities<'a>,
|
||||||
Read<'a, EventBus<ServerEvent>>,
|
Read<'a, EventBus<ServerEvent>>,
|
||||||
ReadExpect<'a, TerrainGrid>,
|
ReadExpect<'a, TerrainGrid>,
|
||||||
|
ReadExpect<'a, Lod>,
|
||||||
ReadExpect<'a, NetworkRequestMetrics>,
|
ReadExpect<'a, NetworkRequestMetrics>,
|
||||||
Write<'a, Vec<ChunkRequest>>,
|
Write<'a, Vec<ChunkRequest>>,
|
||||||
ReadStorage<'a, Pos>,
|
ReadStorage<'a, Pos>,
|
||||||
@ -37,6 +40,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
entities,
|
entities,
|
||||||
server_event_bus,
|
server_event_bus,
|
||||||
terrain,
|
terrain,
|
||||||
|
lod,
|
||||||
network_metrics,
|
network_metrics,
|
||||||
mut chunk_requests,
|
mut chunk_requests,
|
||||||
positions,
|
positions,
|
||||||
@ -101,6 +105,12 @@ impl<'a> System<'a> for Sys {
|
|||||||
network_metrics.chunks_request_dropped.inc();
|
network_metrics.chunks_request_dropped.inc();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ClientGeneral::LodZoneRequest { key } => {
|
||||||
|
client.send(ServerGeneral::LodZoneUpdate {
|
||||||
|
key,
|
||||||
|
zone: lod.zone(key).clone(),
|
||||||
|
})?;
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
debug!(
|
debug!(
|
||||||
"Kicking possibly misbehaving client due to invalud terrain \
|
"Kicking possibly misbehaving client due to invalud terrain \
|
||||||
|
@ -39,6 +39,9 @@ widget_ids! {
|
|||||||
vd_slider,
|
vd_slider,
|
||||||
vd_text,
|
vd_text,
|
||||||
vd_value,
|
vd_value,
|
||||||
|
ld_slider,
|
||||||
|
ld_text,
|
||||||
|
ld_value,
|
||||||
lod_detail_slider,
|
lod_detail_slider,
|
||||||
lod_detail_text,
|
lod_detail_text,
|
||||||
lod_detail_value,
|
lod_detail_value,
|
||||||
@ -280,8 +283,6 @@ impl<'a> Widget for Video<'a> {
|
|||||||
if let Some(new_val) = ImageSlider::discrete(
|
if let Some(new_val) = ImageSlider::discrete(
|
||||||
self.global_state.settings.graphics.view_distance,
|
self.global_state.settings.graphics.view_distance,
|
||||||
1,
|
1,
|
||||||
// FIXME: Move back to 64 once we support multiple texture atlases, or figure out a
|
|
||||||
// way to increase the size of the terrain atlas.
|
|
||||||
65,
|
65,
|
||||||
self.imgs.slider_indicator,
|
self.imgs.slider_indicator,
|
||||||
self.imgs.slider,
|
self.imgs.slider,
|
||||||
@ -306,9 +307,44 @@ impl<'a> Widget for Video<'a> {
|
|||||||
.color(TEXT_COLOR)
|
.color(TEXT_COLOR)
|
||||||
.set(state.ids.vd_value, ui);
|
.set(state.ids.vd_value, ui);
|
||||||
|
|
||||||
|
// LoD Distance
|
||||||
|
Text::new(self.localized_strings.get("hud.settings.lod_distance"))
|
||||||
|
.down_from(state.ids.vd_slider, 10.0)
|
||||||
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
|
.color(TEXT_COLOR)
|
||||||
|
.set(state.ids.ld_text, ui);
|
||||||
|
|
||||||
|
if let Some(new_val) = ImageSlider::discrete(
|
||||||
|
self.global_state.settings.graphics.lod_distance,
|
||||||
|
0,
|
||||||
|
500,
|
||||||
|
self.imgs.slider_indicator,
|
||||||
|
self.imgs.slider,
|
||||||
|
)
|
||||||
|
.w_h(104.0, 22.0)
|
||||||
|
.down_from(state.ids.ld_text, 8.0)
|
||||||
|
.track_breadth(12.0)
|
||||||
|
.slider_length(10.0)
|
||||||
|
.pad_track((5.0, 5.0))
|
||||||
|
.set(state.ids.ld_slider, ui)
|
||||||
|
{
|
||||||
|
events.push(GraphicsChange::AdjustLodDistance(new_val));
|
||||||
|
}
|
||||||
|
|
||||||
|
Text::new(&format!(
|
||||||
|
"{}",
|
||||||
|
self.global_state.settings.graphics.lod_distance
|
||||||
|
))
|
||||||
|
.right_from(state.ids.ld_slider, 8.0)
|
||||||
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
|
.color(TEXT_COLOR)
|
||||||
|
.set(state.ids.ld_value, ui);
|
||||||
|
|
||||||
// Max FPS
|
// Max FPS
|
||||||
Text::new(self.localized_strings.get("hud.settings.maximum_fps"))
|
Text::new(self.localized_strings.get("hud.settings.maximum_fps"))
|
||||||
.down_from(state.ids.vd_slider, 10.0)
|
.down_from(state.ids.ld_slider, 10.0)
|
||||||
.font_size(self.fonts.cyri.scale(14))
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
.font_id(self.fonts.cyri.conrod_id)
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
.color(TEXT_COLOR)
|
.color(TEXT_COLOR)
|
||||||
@ -343,7 +379,7 @@ impl<'a> Widget for Video<'a> {
|
|||||||
|
|
||||||
// Max Background FPS
|
// Max Background FPS
|
||||||
Text::new(self.localized_strings.get("hud.settings.background_fps"))
|
Text::new(self.localized_strings.get("hud.settings.background_fps"))
|
||||||
.down_from(state.ids.vd_slider, 10.0)
|
.down_from(state.ids.ld_slider, 10.0)
|
||||||
.right_from(state.ids.max_fps_value, 30.0)
|
.right_from(state.ids.max_fps_value, 30.0)
|
||||||
.font_size(self.fonts.cyri.scale(14))
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
.font_id(self.fonts.cyri.conrod_id)
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
@ -391,7 +427,7 @@ impl<'a> Widget for Video<'a> {
|
|||||||
|
|
||||||
// Present Mode
|
// Present Mode
|
||||||
Text::new(self.localized_strings.get("hud.settings.present_mode"))
|
Text::new(self.localized_strings.get("hud.settings.present_mode"))
|
||||||
.down_from(state.ids.vd_slider, 10.0)
|
.down_from(state.ids.ld_slider, 10.0)
|
||||||
.right_from(state.ids.max_background_fps_value, 30.0)
|
.right_from(state.ids.max_background_fps_value, 30.0)
|
||||||
.font_size(self.fonts.cyri.scale(14))
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
.font_id(self.fonts.cyri.conrod_id)
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
|
@ -135,8 +135,9 @@ impl PlayState for CharSelectionState {
|
|||||||
{
|
{
|
||||||
let mut c = self.client.borrow_mut();
|
let mut c = self.client.borrow_mut();
|
||||||
c.request_character(character_id);
|
c.request_character(character_id);
|
||||||
//Send our ViewDistance
|
//Send our ViewDistance and LoD distance
|
||||||
c.set_view_distance(global_state.settings.graphics.view_distance);
|
c.set_view_distance(global_state.settings.graphics.view_distance);
|
||||||
|
c.set_lod_distance(global_state.settings.graphics.lod_distance);
|
||||||
}
|
}
|
||||||
return PlayStateResult::Switch(Box::new(SessionState::new(
|
return PlayStateResult::Switch(Box::new(SessionState::new(
|
||||||
global_state,
|
global_state,
|
||||||
|
@ -26,6 +26,7 @@ pub use self::{
|
|||||||
Locals as FigureLocals,
|
Locals as FigureLocals,
|
||||||
},
|
},
|
||||||
fluid::Vertex as FluidVertex,
|
fluid::Vertex as FluidVertex,
|
||||||
|
lod_object::{Instance as LodObjectInstance, Vertex as LodObjectVertex},
|
||||||
lod_terrain::{LodData, Vertex as LodTerrainVertex},
|
lod_terrain::{LodData, Vertex as LodTerrainVertex},
|
||||||
particle::{Instance as ParticleInstance, Vertex as ParticleVertex},
|
particle::{Instance as ParticleInstance, Vertex as ParticleVertex},
|
||||||
postprocess::Locals as PostProcessLocals,
|
postprocess::Locals as PostProcessLocals,
|
||||||
@ -459,4 +460,8 @@ pub enum ExperimentalShader {
|
|||||||
BareMinimum,
|
BareMinimum,
|
||||||
/// Lowers strength of the glow effect for lights near the camera.
|
/// Lowers strength of the glow effect for lights near the camera.
|
||||||
LowGlowNearCamera,
|
LowGlowNearCamera,
|
||||||
|
/// Disable the fake voxel effect on LoD features.
|
||||||
|
NoLodVoxels,
|
||||||
|
// Disable the 'pop-in' effect when loading terrain.
|
||||||
|
NoTerrainPop,
|
||||||
}
|
}
|
||||||
|
157
voxygen/src/render/pipelines/lod_object.rs
Normal file
157
voxygen/src/render/pipelines/lod_object.rs
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
use super::super::{AaMode, GlobalsLayouts, Vertex as VertexTrait};
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use std::mem;
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
||||||
|
pub struct Vertex {
|
||||||
|
pos: [f32; 3],
|
||||||
|
norm: [f32; 3],
|
||||||
|
col: [f32; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vertex {
|
||||||
|
pub fn new(pos: Vec3<f32>, norm: Vec3<f32>, col: Rgb<f32>) -> Self {
|
||||||
|
Self {
|
||||||
|
pos: pos.into_array(),
|
||||||
|
norm: norm.into_array(),
|
||||||
|
col: col.into_array(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||||
|
const ATTRIBUTES: [wgpu::VertexAttribute; 3] =
|
||||||
|
wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x3];
|
||||||
|
wgpu::VertexBufferLayout {
|
||||||
|
array_stride: Self::STRIDE,
|
||||||
|
step_mode: wgpu::InputStepMode::Vertex,
|
||||||
|
attributes: &ATTRIBUTES,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VertexTrait for Vertex {
|
||||||
|
const QUADS_INDEX: Option<wgpu::IndexFormat> = None;
|
||||||
|
const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() as wgpu::BufferAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
||||||
|
pub struct Instance {
|
||||||
|
inst_pos: [f32; 3],
|
||||||
|
inst_col: [u8; 4],
|
||||||
|
flags: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance {
|
||||||
|
pub fn new(inst_pos: Vec3<f32>, col: Rgb<u8>, flags: common::lod::Flags) -> Self {
|
||||||
|
Self {
|
||||||
|
inst_pos: inst_pos.into_array(),
|
||||||
|
inst_col: Rgba::new(col.r, col.g, col.b, 255).into_array(),
|
||||||
|
flags: flags.bits() as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||||
|
const ATTRIBUTES: [wgpu::VertexAttribute; 3] = wgpu::vertex_attr_array![
|
||||||
|
3 => Float32x3,
|
||||||
|
4 => Uint8x4,
|
||||||
|
5 => Uint32,
|
||||||
|
];
|
||||||
|
wgpu::VertexBufferLayout {
|
||||||
|
array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
|
||||||
|
step_mode: wgpu::InputStepMode::Instance,
|
||||||
|
attributes: &ATTRIBUTES,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl Default for Instance {
|
||||||
|
// fn default() -> Self { Self::new(Mat4::identity(), 0.0, 0.0,
|
||||||
|
// Vec3::zero(), 0, 1.0, 0.0, 0) } }
|
||||||
|
|
||||||
|
// TODO: ColLightsWrapper instead?
|
||||||
|
pub struct Locals;
|
||||||
|
|
||||||
|
pub struct LodObjectPipeline {
|
||||||
|
pub pipeline: wgpu::RenderPipeline,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LodObjectPipeline {
|
||||||
|
pub fn new(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
vs_module: &wgpu::ShaderModule,
|
||||||
|
fs_module: &wgpu::ShaderModule,
|
||||||
|
global_layout: &GlobalsLayouts,
|
||||||
|
aa_mode: AaMode,
|
||||||
|
) -> Self {
|
||||||
|
common_base::span!(_guard, "LodObjectPipeline::new");
|
||||||
|
let render_pipeline_layout =
|
||||||
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("LoD object pipeline layout"),
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
bind_group_layouts: &[&global_layout.globals, &global_layout.shadow_textures],
|
||||||
|
});
|
||||||
|
|
||||||
|
let samples = match aa_mode {
|
||||||
|
AaMode::None | AaMode::Fxaa => 1,
|
||||||
|
AaMode::MsaaX4 => 4,
|
||||||
|
AaMode::MsaaX8 => 8,
|
||||||
|
AaMode::MsaaX16 => 16,
|
||||||
|
};
|
||||||
|
|
||||||
|
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("LoD object pipeline"),
|
||||||
|
layout: Some(&render_pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: vs_module,
|
||||||
|
entry_point: "main",
|
||||||
|
buffers: &[Vertex::desc(), Instance::desc()],
|
||||||
|
},
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
|
strip_index_format: None,
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: Some(wgpu::Face::Back),
|
||||||
|
clamp_depth: false,
|
||||||
|
polygon_mode: wgpu::PolygonMode::Fill,
|
||||||
|
conservative: false,
|
||||||
|
},
|
||||||
|
depth_stencil: Some(wgpu::DepthStencilState {
|
||||||
|
format: wgpu::TextureFormat::Depth32Float,
|
||||||
|
depth_write_enabled: true,
|
||||||
|
depth_compare: wgpu::CompareFunction::GreaterEqual,
|
||||||
|
stencil: wgpu::StencilState {
|
||||||
|
front: wgpu::StencilFaceState::IGNORE,
|
||||||
|
back: wgpu::StencilFaceState::IGNORE,
|
||||||
|
read_mask: !0,
|
||||||
|
write_mask: !0,
|
||||||
|
},
|
||||||
|
bias: wgpu::DepthBiasState {
|
||||||
|
constant: 0,
|
||||||
|
slope_scale: 0.0,
|
||||||
|
clamp: 0.0,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: samples,
|
||||||
|
mask: !0,
|
||||||
|
alpha_to_coverage_enabled: false,
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: fs_module,
|
||||||
|
entry_point: "main",
|
||||||
|
targets: &[wgpu::ColorTargetState {
|
||||||
|
format: wgpu::TextureFormat::Rgba16Float,
|
||||||
|
blend: Some(wgpu::BlendState::REPLACE),
|
||||||
|
write_mask: wgpu::ColorWrite::ALL,
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
pipeline: render_pipeline,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ pub mod clouds;
|
|||||||
pub mod debug;
|
pub mod debug;
|
||||||
pub mod figure;
|
pub mod figure;
|
||||||
pub mod fluid;
|
pub mod fluid;
|
||||||
|
pub mod lod_object;
|
||||||
pub mod lod_terrain;
|
pub mod lod_terrain;
|
||||||
pub mod particle;
|
pub mod particle;
|
||||||
pub mod postprocess;
|
pub mod postprocess;
|
||||||
|
@ -4,8 +4,9 @@ use super::{
|
|||||||
instances::Instances,
|
instances::Instances,
|
||||||
model::{DynamicModel, Model, SubModel},
|
model::{DynamicModel, Model, SubModel},
|
||||||
pipelines::{
|
pipelines::{
|
||||||
blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, shadow, skybox,
|
blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle, shadow,
|
||||||
sprite, terrain, trail, ui, ColLights, GlobalsBindGroup, ShadowTexturesBindGroup,
|
skybox, sprite, terrain, trail, ui, ColLights, GlobalsBindGroup,
|
||||||
|
ShadowTexturesBindGroup,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Renderer, ShadowMap, ShadowMapRenderer,
|
Renderer, ShadowMap, ShadowMapRenderer,
|
||||||
@ -764,6 +765,15 @@ impl<'pass> FirstPassDrawer<'pass> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn draw_lod_objects(&mut self) -> LodObjectDrawer<'_, 'pass> {
|
||||||
|
let mut render_pass = self.render_pass.scope("lod objects", self.borrow.device);
|
||||||
|
|
||||||
|
render_pass.set_pipeline(&self.pipelines.lod_object.pipeline);
|
||||||
|
set_quad_index_buffer::<lod_object::Vertex>(&mut render_pass, self.borrow);
|
||||||
|
|
||||||
|
LodObjectDrawer { render_pass }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn draw_fluid(&mut self) -> FluidDrawer<'_, 'pass> {
|
pub fn draw_fluid(&mut self) -> FluidDrawer<'_, 'pass> {
|
||||||
let mut render_pass = self.render_pass.scope("fluid", self.borrow.device);
|
let mut render_pass = self.render_pass.scope("fluid", self.borrow.device);
|
||||||
|
|
||||||
@ -909,6 +919,25 @@ impl<'pass_ref, 'pass: 'pass_ref> Drop for SpriteDrawer<'pass_ref, 'pass> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub struct LodObjectDrawer<'pass_ref, 'pass: 'pass_ref> {
|
||||||
|
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'pass_ref, 'pass: 'pass_ref> LodObjectDrawer<'pass_ref, 'pass> {
|
||||||
|
pub fn draw<'data: 'pass>(
|
||||||
|
&mut self,
|
||||||
|
model: &'data Model<lod_object::Vertex>,
|
||||||
|
instances: &'data Instances<lod_object::Instance>,
|
||||||
|
) {
|
||||||
|
self.render_pass.set_vertex_buffer(0, model.buf().slice(..));
|
||||||
|
self.render_pass
|
||||||
|
.set_vertex_buffer(1, instances.buf().slice(..));
|
||||||
|
self.render_pass
|
||||||
|
.draw(0..model.len() as u32, 0..instances.count() as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct FluidDrawer<'pass_ref, 'pass: 'pass_ref> {
|
pub struct FluidDrawer<'pass_ref, 'pass: 'pass_ref> {
|
||||||
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
|
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use super::{
|
use super::{
|
||||||
super::{
|
super::{
|
||||||
pipelines::{
|
pipelines::{
|
||||||
blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, postprocess, shadow,
|
blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle,
|
||||||
skybox, sprite, terrain, trail, ui,
|
postprocess, shadow, skybox, sprite, terrain, trail, ui,
|
||||||
},
|
},
|
||||||
AaMode, BloomMode, CloudMode, FluidMode, LightingMode, PipelineModes, RenderError,
|
AaMode, BloomMode, CloudMode, FluidMode, LightingMode, PipelineModes, RenderError,
|
||||||
ShadowMode,
|
ShadowMode,
|
||||||
@ -28,6 +28,7 @@ pub struct Pipelines {
|
|||||||
// player_shadow: figure::FigurePipeline,
|
// player_shadow: figure::FigurePipeline,
|
||||||
pub skybox: skybox::SkyboxPipeline,
|
pub skybox: skybox::SkyboxPipeline,
|
||||||
pub sprite: sprite::SpritePipeline,
|
pub sprite: sprite::SpritePipeline,
|
||||||
|
pub lod_object: lod_object::LodObjectPipeline,
|
||||||
pub terrain: terrain::TerrainPipeline,
|
pub terrain: terrain::TerrainPipeline,
|
||||||
pub ui: ui::UiPipeline,
|
pub ui: ui::UiPipeline,
|
||||||
pub blit: blit::BlitPipeline,
|
pub blit: blit::BlitPipeline,
|
||||||
@ -49,6 +50,7 @@ pub struct IngamePipelines {
|
|||||||
// player_shadow: figure::FigurePipeline,
|
// player_shadow: figure::FigurePipeline,
|
||||||
skybox: skybox::SkyboxPipeline,
|
skybox: skybox::SkyboxPipeline,
|
||||||
sprite: sprite::SpritePipeline,
|
sprite: sprite::SpritePipeline,
|
||||||
|
lod_object: lod_object::LodObjectPipeline,
|
||||||
terrain: terrain::TerrainPipeline,
|
terrain: terrain::TerrainPipeline,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +87,7 @@ impl Pipelines {
|
|||||||
//player_shadow: ingame.player_shadow,
|
//player_shadow: ingame.player_shadow,
|
||||||
skybox: ingame.skybox,
|
skybox: ingame.skybox,
|
||||||
sprite: ingame.sprite,
|
sprite: ingame.sprite,
|
||||||
|
lod_object: ingame.lod_object,
|
||||||
terrain: ingame.terrain,
|
terrain: ingame.terrain,
|
||||||
ui: interface.ui,
|
ui: interface.ui,
|
||||||
blit: interface.blit,
|
blit: interface.blit,
|
||||||
@ -106,6 +109,8 @@ struct ShaderModules {
|
|||||||
fluid_frag: wgpu::ShaderModule,
|
fluid_frag: wgpu::ShaderModule,
|
||||||
sprite_vert: wgpu::ShaderModule,
|
sprite_vert: wgpu::ShaderModule,
|
||||||
sprite_frag: wgpu::ShaderModule,
|
sprite_frag: wgpu::ShaderModule,
|
||||||
|
lod_object_vert: wgpu::ShaderModule,
|
||||||
|
lod_object_frag: wgpu::ShaderModule,
|
||||||
particle_vert: wgpu::ShaderModule,
|
particle_vert: wgpu::ShaderModule,
|
||||||
particle_frag: wgpu::ShaderModule,
|
particle_frag: wgpu::ShaderModule,
|
||||||
trail_vert: wgpu::ShaderModule,
|
trail_vert: wgpu::ShaderModule,
|
||||||
@ -293,6 +298,8 @@ impl ShaderModules {
|
|||||||
fluid_frag: create_shader(&selected_fluid_shader, ShaderKind::Fragment)?,
|
fluid_frag: create_shader(&selected_fluid_shader, ShaderKind::Fragment)?,
|
||||||
sprite_vert: create_shader("sprite-vert", ShaderKind::Vertex)?,
|
sprite_vert: create_shader("sprite-vert", ShaderKind::Vertex)?,
|
||||||
sprite_frag: create_shader("sprite-frag", ShaderKind::Fragment)?,
|
sprite_frag: create_shader("sprite-frag", ShaderKind::Fragment)?,
|
||||||
|
lod_object_vert: create_shader("lod-object-vert", ShaderKind::Vertex)?,
|
||||||
|
lod_object_frag: create_shader("lod-object-frag", ShaderKind::Fragment)?,
|
||||||
particle_vert: create_shader("particle-vert", ShaderKind::Vertex)?,
|
particle_vert: create_shader("particle-vert", ShaderKind::Vertex)?,
|
||||||
particle_frag: create_shader("particle-frag", ShaderKind::Fragment)?,
|
particle_frag: create_shader("particle-frag", ShaderKind::Fragment)?,
|
||||||
trail_vert: create_shader("trail-vert", ShaderKind::Vertex)?,
|
trail_vert: create_shader("trail-vert", ShaderKind::Vertex)?,
|
||||||
@ -415,7 +422,7 @@ fn create_ingame_and_shadow_pipelines(
|
|||||||
needs: PipelineNeeds,
|
needs: PipelineNeeds,
|
||||||
pool: &rayon::ThreadPool,
|
pool: &rayon::ThreadPool,
|
||||||
// TODO: Reduce the boilerplate in this file
|
// TODO: Reduce the boilerplate in this file
|
||||||
tasks: [Task; 15],
|
tasks: [Task; 16],
|
||||||
) -> IngameAndShadowPipelines {
|
) -> IngameAndShadowPipelines {
|
||||||
prof_span!(_guard, "create_ingame_and_shadow_pipelines");
|
prof_span!(_guard, "create_ingame_and_shadow_pipelines");
|
||||||
|
|
||||||
@ -434,6 +441,7 @@ fn create_ingame_and_shadow_pipelines(
|
|||||||
terrain_task,
|
terrain_task,
|
||||||
fluid_task,
|
fluid_task,
|
||||||
sprite_task,
|
sprite_task,
|
||||||
|
lod_object_task,
|
||||||
particle_task,
|
particle_task,
|
||||||
trail_task,
|
trail_task,
|
||||||
lod_terrain_task,
|
lod_terrain_task,
|
||||||
@ -546,6 +554,21 @@ fn create_ingame_and_shadow_pipelines(
|
|||||||
"sprite pipeline creation",
|
"sprite pipeline creation",
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
// Pipeline for rendering lod objects
|
||||||
|
let create_lod_object = || {
|
||||||
|
lod_object_task.run(
|
||||||
|
|| {
|
||||||
|
lod_object::LodObjectPipeline::new(
|
||||||
|
device,
|
||||||
|
&shaders.lod_object_vert,
|
||||||
|
&shaders.lod_object_frag,
|
||||||
|
&layouts.global,
|
||||||
|
pipeline_modes.aa,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"lod object pipeline creation",
|
||||||
|
)
|
||||||
|
};
|
||||||
// Pipeline for rendering particles
|
// Pipeline for rendering particles
|
||||||
let create_particle = || {
|
let create_particle = || {
|
||||||
particle_task.run(
|
particle_task.run(
|
||||||
@ -732,6 +755,7 @@ fn create_ingame_and_shadow_pipelines(
|
|||||||
create_figure_directed_shadow,
|
create_figure_directed_shadow,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
let j7 = create_lod_object;
|
||||||
|
|
||||||
// Ignore this
|
// Ignore this
|
||||||
let (
|
let (
|
||||||
@ -739,10 +763,13 @@ fn create_ingame_and_shadow_pipelines(
|
|||||||
((debug, (skybox, figure)), (terrain, (fluid, bloom))),
|
((debug, (skybox, figure)), (terrain, (fluid, bloom))),
|
||||||
((sprite, particle), (lod_terrain, (clouds, trail))),
|
((sprite, particle), (lod_terrain, (clouds, trail))),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
((postprocess, point_shadow), (terrain_directed_shadow, figure_directed_shadow)),
|
((postprocess, point_shadow), (terrain_directed_shadow, figure_directed_shadow)),
|
||||||
|
lod_object,
|
||||||
|
),
|
||||||
) = pool.join(
|
) = pool.join(
|
||||||
|| pool.join(|| pool.join(j1, j2), || pool.join(j3, j4)),
|
|| pool.join(|| pool.join(j1, j2), || pool.join(j3, j4)),
|
||||||
|| pool.join(j5, j6),
|
|| pool.join(|| pool.join(j5, j6), j7),
|
||||||
);
|
);
|
||||||
|
|
||||||
IngameAndShadowPipelines {
|
IngameAndShadowPipelines {
|
||||||
@ -758,6 +785,7 @@ fn create_ingame_and_shadow_pipelines(
|
|||||||
postprocess,
|
postprocess,
|
||||||
skybox,
|
skybox,
|
||||||
sprite,
|
sprite,
|
||||||
|
lod_object,
|
||||||
terrain,
|
terrain,
|
||||||
// player_shadow_pipeline,
|
// player_shadow_pipeline,
|
||||||
},
|
},
|
||||||
|
@ -61,6 +61,8 @@ impl assets::Compound for Shaders {
|
|||||||
"fluid-frag.shiny",
|
"fluid-frag.shiny",
|
||||||
"sprite-vert",
|
"sprite-vert",
|
||||||
"sprite-frag",
|
"sprite-frag",
|
||||||
|
"lod-object-vert",
|
||||||
|
"lod-object-frag",
|
||||||
"particle-vert",
|
"particle-vert",
|
||||||
"particle-frag",
|
"particle-frag",
|
||||||
"trail-vert",
|
"trail-vert",
|
||||||
|
@ -1,17 +1,41 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
render::{
|
render::{
|
||||||
pipelines::lod_terrain::{LodData, Vertex},
|
pipelines::lod_terrain::{LodData, Vertex},
|
||||||
FirstPassDrawer, LodTerrainVertex, Mesh, Model, Quad, Renderer,
|
FirstPassDrawer, Instances, LodObjectInstance, LodObjectVertex, LodTerrainVertex, Mesh,
|
||||||
|
Model, Quad, Renderer, Tri,
|
||||||
},
|
},
|
||||||
|
scene::{camera, Camera},
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
};
|
};
|
||||||
use client::Client;
|
use client::Client;
|
||||||
use common::{spiral::Spiral2d, util::srgba_to_linear};
|
use common::{
|
||||||
|
assets::{AssetExt, ObjAsset},
|
||||||
|
lod,
|
||||||
|
spiral::Spiral2d,
|
||||||
|
util::srgba_to_linear,
|
||||||
|
};
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use std::ops::Range;
|
||||||
|
use treeculler::{BVol, Frustum, AABB};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
// For culling
|
||||||
|
const MAX_OBJECT_RADIUS: i32 = 64;
|
||||||
|
|
||||||
|
struct ObjectGroup {
|
||||||
|
instances: Instances<LodObjectInstance>,
|
||||||
|
// None implies no instances
|
||||||
|
z_range: Option<Range<i32>>,
|
||||||
|
frustum_last_plane_index: u8,
|
||||||
|
visible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Lod {
|
pub struct Lod {
|
||||||
model: Option<(u32, Model<LodTerrainVertex>)>,
|
model: Option<(u32, Model<LodTerrainVertex>)>,
|
||||||
data: LodData,
|
data: LodData,
|
||||||
|
|
||||||
|
zone_objects: HashMap<Vec2<i32>, HashMap<lod::ObjectKind, ObjectGroup>>,
|
||||||
|
object_data: HashMap<lod::ObjectKind, Model<LodObjectVertex>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make constant when possible.
|
// TODO: Make constant when possible.
|
||||||
@ -22,9 +46,7 @@ pub fn water_color() -> Rgba<f32> {
|
|||||||
|
|
||||||
impl Lod {
|
impl Lod {
|
||||||
pub fn new(renderer: &mut Renderer, client: &Client, settings: &Settings) -> Self {
|
pub fn new(renderer: &mut Renderer, client: &Client, settings: &Settings) -> Self {
|
||||||
Self {
|
let data = LodData::new(
|
||||||
model: None,
|
|
||||||
data: LodData::new(
|
|
||||||
renderer,
|
renderer,
|
||||||
client.world_data().chunk_size().as_(),
|
client.world_data().chunk_size().as_(),
|
||||||
client.world_data().lod_base.raw(),
|
client.world_data().lod_base.raw(),
|
||||||
@ -33,7 +55,22 @@ impl Lod {
|
|||||||
settings.graphics.lod_detail.max(100).min(2500),
|
settings.graphics.lod_detail.max(100).min(2500),
|
||||||
/* TODO: figure out how we want to do this without color borders?
|
/* TODO: figure out how we want to do this without color borders?
|
||||||
* water_color().into_array().into(), */
|
* water_color().into_array().into(), */
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
model: None,
|
||||||
|
data,
|
||||||
|
zone_objects: HashMap::new(),
|
||||||
|
object_data: [
|
||||||
|
(lod::ObjectKind::Oak, make_lod_object("oak", renderer)),
|
||||||
|
(lod::ObjectKind::Pine, make_lod_object("pine", renderer)),
|
||||||
|
(lod::ObjectKind::House, make_lod_object("house", renderer)),
|
||||||
|
(
|
||||||
|
lod::ObjectKind::GiantTree,
|
||||||
|
make_lod_object("giant_tree", renderer),
|
||||||
),
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +81,14 @@ impl Lod {
|
|||||||
self.data.tgt_detail = (detail - detail % 2).max(100).min(2500);
|
self.data.tgt_detail = (detail - detail % 2).max(100).min(2500);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn maintain(&mut self, renderer: &mut Renderer) {
|
pub fn maintain(
|
||||||
|
&mut self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
client: &Client,
|
||||||
|
focus_pos: Vec3<f32>,
|
||||||
|
camera: &Camera,
|
||||||
|
) {
|
||||||
|
// Update LoD terrain mesh according to detail
|
||||||
if self
|
if self
|
||||||
.model
|
.model
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -58,12 +102,100 @@ impl Lod {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create new LoD groups when a new zone has loaded
|
||||||
|
for (p, zone) in client.lod_zones() {
|
||||||
|
self.zone_objects.entry(*p).or_insert_with(|| {
|
||||||
|
let mut objects = HashMap::<_, Vec<_>>::new();
|
||||||
|
let mut z_range = None;
|
||||||
|
for object in zone.objects.iter() {
|
||||||
|
let pos = p.map(|e| lod::to_wpos(e) as f32).with_z(0.0)
|
||||||
|
+ object.pos.map(|e| e as f32)
|
||||||
|
+ Vec2::broadcast(0.5).with_z(0.0);
|
||||||
|
z_range = Some(z_range.map_or(
|
||||||
|
pos.z as i32..pos.z as i32,
|
||||||
|
|z_range: Range<i32>| {
|
||||||
|
z_range.start.min(pos.z as i32)..z_range.end.max(pos.z as i32)
|
||||||
|
},
|
||||||
|
));
|
||||||
|
// TODO: Put this somewhere more easily configurable, like a manifest
|
||||||
|
let color = match object.kind {
|
||||||
|
lod::ObjectKind::Pine => Rgb::new(0, 25, 12),
|
||||||
|
lod::ObjectKind::Oak => Rgb::new(10, 50, 5),
|
||||||
|
lod::ObjectKind::House => Rgb::new(20, 15, 0),
|
||||||
|
lod::ObjectKind::GiantTree => Rgb::new(8, 35, 5),
|
||||||
|
};
|
||||||
|
objects
|
||||||
|
.entry(object.kind)
|
||||||
|
.or_default()
|
||||||
|
.push(LodObjectInstance::new(pos, color, object.flags));
|
||||||
|
}
|
||||||
|
objects
|
||||||
|
.into_iter()
|
||||||
|
.map(|(kind, instances)| {
|
||||||
|
(kind, ObjectGroup {
|
||||||
|
instances: renderer
|
||||||
|
.create_instances(&instances)
|
||||||
|
.expect("Renderer error?!"),
|
||||||
|
z_range: z_range.clone(),
|
||||||
|
frustum_last_plane_index: 0,
|
||||||
|
visible: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove zones that are unloaded
|
||||||
|
self.zone_objects
|
||||||
|
.retain(|p, _| client.lod_zones().contains_key(p));
|
||||||
|
|
||||||
|
// Determine visiblity of zones based on view frustum
|
||||||
|
let camera::Dependents {
|
||||||
|
view_mat,
|
||||||
|
proj_mat_treeculler,
|
||||||
|
..
|
||||||
|
} = camera.dependents();
|
||||||
|
let focus_off = focus_pos.map(|e| e.trunc());
|
||||||
|
let frustum = Frustum::from_modelview_projection(
|
||||||
|
(proj_mat_treeculler * view_mat * Mat4::translation_3d(-focus_off)).into_col_arrays(),
|
||||||
|
);
|
||||||
|
for (pos, groups) in &mut self.zone_objects {
|
||||||
|
for group in groups.values_mut() {
|
||||||
|
if let Some(z_range) = &group.z_range {
|
||||||
|
let group_min = (pos.map(lod::to_wpos).with_z(z_range.start)
|
||||||
|
- MAX_OBJECT_RADIUS)
|
||||||
|
.map(|e| e as f32);
|
||||||
|
let group_max = ((pos + 1).map(lod::to_wpos).with_z(z_range.end)
|
||||||
|
+ MAX_OBJECT_RADIUS)
|
||||||
|
.map(|e| e as f32);
|
||||||
|
let (in_frustum, last_plane_index) =
|
||||||
|
AABB::new(group_min.into_array(), group_max.into_array())
|
||||||
|
.coherent_test_against_frustum(
|
||||||
|
&frustum,
|
||||||
|
group.frustum_last_plane_index,
|
||||||
|
);
|
||||||
|
group.visible = in_frustum;
|
||||||
|
group.frustum_last_plane_index = last_plane_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>) {
|
pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>) {
|
||||||
if let Some((_, model)) = self.model.as_ref() {
|
if let Some((_, model)) = self.model.as_ref() {
|
||||||
drawer.draw_lod_terrain(model);
|
drawer.draw_lod_terrain(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw LoD objects
|
||||||
|
let mut drawer = drawer.draw_lod_objects();
|
||||||
|
for groups in self.zone_objects.values() {
|
||||||
|
for (kind, group) in groups.iter().filter(|(_, g)| g.visible) {
|
||||||
|
if let Some(model) = self.object_data.get(kind) {
|
||||||
|
drawer.draw(model, &group.instances);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,3 +226,24 @@ fn create_lod_terrain_mesh(detail: u32) -> Mesh<LodTerrainVertex> {
|
|||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_lod_object(name: &str, renderer: &mut Renderer) -> Model<LodObjectVertex> {
|
||||||
|
let model = ObjAsset::load_expect(&format!("voxygen.lod.{}", name));
|
||||||
|
let mesh = model
|
||||||
|
.read()
|
||||||
|
.0
|
||||||
|
.triangles()
|
||||||
|
.map(|vs| {
|
||||||
|
let [a, b, c] = vs.map(|v| {
|
||||||
|
LodObjectVertex::new(
|
||||||
|
v.position().into(),
|
||||||
|
v.normal().unwrap_or([0.0, 0.0, 1.0]).into(),
|
||||||
|
Rgb::broadcast(1.0),
|
||||||
|
//v.color().unwrap_or([1.0; 3]).into(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Tri::new(a, b, c)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
renderer.create_model(&mesh).expect("Mesh was empty!")
|
||||||
|
}
|
||||||
|
@ -681,7 +681,7 @@ impl Scene {
|
|||||||
renderer.update_postprocess_locals(PostProcessLocals::new(proj_mat_inv, view_mat_inv));
|
renderer.update_postprocess_locals(PostProcessLocals::new(proj_mat_inv, view_mat_inv));
|
||||||
|
|
||||||
// Maintain LoD.
|
// Maintain LoD.
|
||||||
self.lod.maintain(renderer);
|
self.lod.maintain(renderer, client, focus_pos, &self.camera);
|
||||||
|
|
||||||
// Maintain debug shapes
|
// Maintain debug shapes
|
||||||
self.debug.maintain(renderer);
|
self.debug.maintain(renderer);
|
||||||
|
@ -69,6 +69,7 @@ pub enum Gameplay {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Graphics {
|
pub enum Graphics {
|
||||||
AdjustViewDistance(u32),
|
AdjustViewDistance(u32),
|
||||||
|
AdjustLodDistance(u32),
|
||||||
AdjustLodDetail(u32),
|
AdjustLodDetail(u32),
|
||||||
AdjustSpriteRenderDistance(u32),
|
AdjustSpriteRenderDistance(u32),
|
||||||
AdjustFigureLoDRenderDistance(u32),
|
AdjustFigureLoDRenderDistance(u32),
|
||||||
@ -344,6 +345,14 @@ impl SettingsChange {
|
|||||||
|
|
||||||
settings.graphics.view_distance = view_distance;
|
settings.graphics.view_distance = view_distance;
|
||||||
},
|
},
|
||||||
|
Graphics::AdjustLodDistance(lod_distance) => {
|
||||||
|
session_state
|
||||||
|
.client
|
||||||
|
.borrow_mut()
|
||||||
|
.set_lod_distance(lod_distance);
|
||||||
|
|
||||||
|
settings.graphics.lod_distance = lod_distance;
|
||||||
|
},
|
||||||
Graphics::AdjustLodDetail(lod_detail) => {
|
Graphics::AdjustLodDetail(lod_detail) => {
|
||||||
session_state.scene.lod.set_detail(lod_detail);
|
session_state.scene.lod.set_detail(lod_detail);
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ impl fmt::Display for Fps {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct GraphicsSettings {
|
pub struct GraphicsSettings {
|
||||||
pub view_distance: u32,
|
pub view_distance: u32,
|
||||||
|
pub lod_distance: u32,
|
||||||
pub sprite_render_distance: u32,
|
pub sprite_render_distance: u32,
|
||||||
pub particles_enabled: bool,
|
pub particles_enabled: bool,
|
||||||
pub lossy_terrain_compression: bool,
|
pub lossy_terrain_compression: bool,
|
||||||
@ -51,6 +52,7 @@ impl Default for GraphicsSettings {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
view_distance: 10,
|
view_distance: 10,
|
||||||
|
lod_distance: 200,
|
||||||
sprite_render_distance: 100,
|
sprite_render_distance: 100,
|
||||||
particles_enabled: true,
|
particles_enabled: true,
|
||||||
lossy_terrain_compression: false,
|
lossy_terrain_compression: false,
|
||||||
|
@ -3,7 +3,7 @@ use crate::{
|
|||||||
block::block_from_structure,
|
block::block_from_structure,
|
||||||
column::ColumnGen,
|
column::ColumnGen,
|
||||||
util::{gen_cache::StructureGenCache, RandomPerm, Sampler, UnitChooser},
|
util::{gen_cache::StructureGenCache, RandomPerm, Sampler, UnitChooser},
|
||||||
Canvas,
|
Canvas, ColumnSample,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
assets::AssetHandle,
|
assets::AssetHandle,
|
||||||
@ -33,6 +33,23 @@ static MODEL_RAND: RandomPerm = RandomPerm::new(0xDB21C052);
|
|||||||
static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7);
|
static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7);
|
||||||
static QUIRKY_RAND: RandomPerm = RandomPerm::new(0xA634460F);
|
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 {
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((seed.wrapping_mul(13)) & 0xFF) as f32 / 256.0 > col.tree_density {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_trees_to(
|
pub fn apply_trees_to(
|
||||||
canvas: &mut Canvas,
|
canvas: &mut Canvas,
|
||||||
dynamic_rng: &mut impl Rng,
|
dynamic_rng: &mut impl Rng,
|
||||||
@ -68,17 +85,7 @@ pub fn apply_trees_to(
|
|||||||
|
|
||||||
let col = ColumnGen::new(info.chunks()).get((wpos, info.index(), calendar))?;
|
let col = ColumnGen::new(info.chunks()).get((wpos, info.index(), calendar))?;
|
||||||
|
|
||||||
// Ensure that it's valid to place a *thing* here
|
if !tree_valid_at(&col, seed) {
|
||||||
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)
|
|
||||||
{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that it's valid to place a tree here
|
|
||||||
if ((seed.wrapping_mul(13)) & 0xFF) as f32 / 256.0 > col.tree_density {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
105
world/src/lib.rs
105
world/src/lib.rs
@ -51,6 +51,7 @@ use common::{
|
|||||||
assets,
|
assets,
|
||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
generation::{ChunkSupplement, EntityInfo},
|
generation::{ChunkSupplement, EntityInfo},
|
||||||
|
lod,
|
||||||
resources::TimeOfDay,
|
resources::TimeOfDay,
|
||||||
terrain::{
|
terrain::{
|
||||||
Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize, TerrainGrid,
|
Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize, TerrainGrid,
|
||||||
@ -60,6 +61,7 @@ use common::{
|
|||||||
use common_net::msg::{world_msg, WorldMapMsg};
|
use common_net::msg::{world_msg, WorldMapMsg};
|
||||||
use rand::{prelude::*, Rng};
|
use rand::{prelude::*, Rng};
|
||||||
use rand_chacha::ChaCha8Rng;
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
use rayon::iter::ParallelIterator;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
@ -463,4 +465,107 @@ impl World {
|
|||||||
|
|
||||||
Ok((chunk, supplement))
|
Ok((chunk, supplement))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zone coordinates
|
||||||
|
pub fn get_lod_zone(&self, pos: Vec2<i32>, index: IndexRef) -> lod::Zone {
|
||||||
|
let min_wpos = pos.map(lod::to_wpos);
|
||||||
|
let max_wpos = (pos + 1).map(lod::to_wpos);
|
||||||
|
|
||||||
|
let mut objects = Vec::new();
|
||||||
|
|
||||||
|
// Add trees
|
||||||
|
objects.append(
|
||||||
|
&mut self
|
||||||
|
.sim()
|
||||||
|
.get_area_trees(min_wpos, max_wpos)
|
||||||
|
.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))
|
||||||
|
.zip(Some(attr))
|
||||||
|
})
|
||||||
|
.filter_map(|(col, tree)| {
|
||||||
|
Some(lod::Object {
|
||||||
|
kind: match tree.forest_kind {
|
||||||
|
all::ForestKind::Oak => lod::ObjectKind::Oak,
|
||||||
|
all::ForestKind::Pine | all::ForestKind::Frostpine => {
|
||||||
|
lod::ObjectKind::Pine
|
||||||
|
},
|
||||||
|
_ => lod::ObjectKind::Oak,
|
||||||
|
},
|
||||||
|
pos: {
|
||||||
|
let rpos = tree.pos - min_wpos;
|
||||||
|
if rpos.is_any_negative() {
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
rpos.map(|e| e as i16).with_z(
|
||||||
|
self.sim().get_alt_approx(tree.pos).unwrap_or(0.0) as i16,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
flags: lod::Flags::empty()
|
||||||
|
| if col.snow_cover {
|
||||||
|
lod::Flags::SNOW_COVERED
|
||||||
|
} else {
|
||||||
|
lod::Flags::empty()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add buildings
|
||||||
|
objects.extend(
|
||||||
|
index
|
||||||
|
.sites
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, site)| {
|
||||||
|
site.get_origin()
|
||||||
|
.map2(min_wpos.zip(max_wpos), |e, (min, max)| e >= min && e < max)
|
||||||
|
.reduce_and()
|
||||||
|
})
|
||||||
|
.filter_map(|(_, site)| match &site.kind {
|
||||||
|
SiteKind::Refactor(site) => {
|
||||||
|
Some(site.plots().filter_map(|plot| match &plot.kind {
|
||||||
|
site2::plot::PlotKind::House(_) => Some(site.tile_wpos(plot.root_tile)),
|
||||||
|
_ => None,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.map(|wpos2d| lod::Object {
|
||||||
|
kind: lod::ObjectKind::House,
|
||||||
|
pos: (wpos2d - min_wpos)
|
||||||
|
.map(|e| e as i16)
|
||||||
|
.with_z(self.sim().get_alt_approx(wpos2d).unwrap_or(0.0) as i16),
|
||||||
|
flags: lod::Flags::empty(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add giant trees
|
||||||
|
objects.extend(
|
||||||
|
index
|
||||||
|
.sites
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, site)| {
|
||||||
|
site.get_origin()
|
||||||
|
.map2(min_wpos.zip(max_wpos), |e, (min, max)| e >= min && e < max)
|
||||||
|
.reduce_and()
|
||||||
|
})
|
||||||
|
.filter(|(_, site)| matches!(&site.kind, SiteKind::GiantTree(_)))
|
||||||
|
.map(|(_, site)| lod::Object {
|
||||||
|
kind: lod::ObjectKind::GiantTree,
|
||||||
|
pos: {
|
||||||
|
let wpos2d = site.get_origin();
|
||||||
|
(wpos2d - min_wpos)
|
||||||
|
.map(|e| e as i16)
|
||||||
|
.with_z(self.sim().get_alt_approx(wpos2d).unwrap_or(0.0) as i16)
|
||||||
|
},
|
||||||
|
flags: lod::Flags::empty(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
lod::Zone { objects }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2134,7 +2134,6 @@ impl WorldSim {
|
|||||||
/// them spawning).
|
/// them spawning).
|
||||||
pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
|
pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
|
||||||
// Deterministic based on wpos
|
// Deterministic based on wpos
|
||||||
let normal_trees =
|
|
||||||
self.gen_ctx
|
self.gen_ctx
|
||||||
.structure_gen
|
.structure_gen
|
||||||
.get(wpos)
|
.get(wpos)
|
||||||
@ -2148,22 +2147,27 @@ impl WorldSim {
|
|||||||
forest_kind: *lottery.choose_seeded(seed).as_ref()?,
|
forest_kind: *lottery.choose_seeded(seed).as_ref()?,
|
||||||
inhabited: false,
|
inhabited: false,
|
||||||
})
|
})
|
||||||
});
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// // For testing
|
pub fn get_area_trees(
|
||||||
// let giant_trees =
|
&self,
|
||||||
// std::array::IntoIter::new(self.gen_ctx.big_structure_gen.get(wpos))
|
wpos_min: Vec2<i32>,
|
||||||
// // Don't even consider trees if we aren't close
|
wpos_max: Vec2<i32>,
|
||||||
// .filter(move |(pos, _)| pos.distance_squared(wpos) < 512i32.pow(2))
|
) -> impl ParallelIterator<Item = TreeAttr> + '_ {
|
||||||
// .map(move |(pos, seed)| TreeAttr {
|
self.gen_ctx
|
||||||
// pos,
|
.structure_gen
|
||||||
// seed,
|
.par_iter(wpos_min, wpos_max)
|
||||||
// scale: 5.0,
|
.filter_map(move |(wpos, seed)| {
|
||||||
// forest_kind: ForestKind::Giant,
|
let lottery = self.make_forest_lottery(wpos);
|
||||||
// inhabited: (seed / 13) % 2 == 0,
|
Some(TreeAttr {
|
||||||
// });
|
pos: wpos,
|
||||||
|
seed,
|
||||||
normal_trees //.chain(giant_trees)
|
scale: 1.0,
|
||||||
|
forest_kind: *lottery.choose_seeded(seed).as_ref()?,
|
||||||
|
inhabited: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user