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
|
||||
*.ico 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
|
||||
|
||||
*.ron gitlab-language=rust
|
||||
|
@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Waypoints saved between sessions and shared with group members.
|
||||
- New rocks
|
||||
- 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
|
||||
- Added Belarusian translation
|
||||
- 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
|
||||
|
||||
|
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -6560,6 +6560,7 @@ dependencies = [
|
||||
"serde",
|
||||
"tracing",
|
||||
"walkdir 2.3.2",
|
||||
"wavefront",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -7340,6 +7341,15 @@ dependencies = [
|
||||
"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]]
|
||||
name = "wayland-client"
|
||||
version = "0.28.6"
|
||||
|
@ -59,6 +59,7 @@
|
||||
"hud.settings.reset_gameplay": "Reset to Defaults",
|
||||
|
||||
"hud.settings.view_distance": "View Distance",
|
||||
"hud.settings.lod_distance": "LoD Distance",
|
||||
"hud.settings.sprites_view_distance": "Sprites View Distance",
|
||||
"hud.settings.figures_view_distance": "Entities View Distance",
|
||||
"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);
|
||||
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;
|
||||
|
||||
// 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 -= min(32.0, 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0));
|
||||
|
||||
f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.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 -= 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
|
||||
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(
|
||||
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
|
||||
) * 0.15 / pow(norm.z + 0.1, 3);
|
||||
) * 0.25 / pow(norm.z + 0.1, 3);
|
||||
norm = normalize(norm);
|
||||
#endif
|
||||
|
||||
@ -373,6 +373,7 @@ vec3 lod_col(vec2 pos) {
|
||||
|
||||
vec3 col = textureBicubic(t_map, s_map, pos_to_tex(pos)).rgb;
|
||||
|
||||
/*
|
||||
#ifdef EXPERIMENTAL_PROCEDURALLODDETAIL
|
||||
col *= pow(vec3(
|
||||
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
|
||||
) + 1.0, vec3(0.5));
|
||||
#endif
|
||||
*/
|
||||
|
||||
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,26 +333,30 @@ void main() {
|
||||
hit_xy ? vec3(0.0, 0.0, sides.z) : vec3(0.0, 0.0, 0.0);
|
||||
*/
|
||||
vec3 voxel_norm;
|
||||
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
#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;
|
||||
// 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 {
|
||||
voxel_norm = vec3(0, sign(cam_dir.y), 0);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
#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);
|
||||
// delta_sides *= sqrt(1.0 - f_ao_view * f_ao_view);
|
||||
|
@ -81,8 +81,10 @@ void main() {
|
||||
|
||||
// Terrain 'pop-in' effect
|
||||
#ifndef EXPERIMENTAL_BAREMINIMUM
|
||||
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));
|
||||
#ifndef EXPERIMENTAL_NOTERRAINPOP
|
||||
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));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef EXPERIMENTAL_CURVEDWORLD
|
||||
|
@ -35,10 +35,12 @@ use common::{
|
||||
event::{EventBus, LocalEvent},
|
||||
grid::Grid,
|
||||
link::Is,
|
||||
lod,
|
||||
mounting::Rider,
|
||||
outcome::Outcome,
|
||||
recipe::RecipeBook,
|
||||
resources::{PlayerEntity, TimeOfDay},
|
||||
spiral::Spiral2d,
|
||||
terrain::{
|
||||
block::Block, map::MapConfig, neighbors, BiomeKind, SitesKind, SpriteKind, TerrainChunk,
|
||||
TerrainChunkSize,
|
||||
@ -171,10 +173,12 @@ pub struct Client {
|
||||
pub chat_mode: ChatMode,
|
||||
recipe_book: RecipeBook,
|
||||
available_recipes: HashMap<String, Option<SpriteKind>>,
|
||||
lod_zones: HashMap<Vec2<i32>, lod::Zone>,
|
||||
lod_last_requested: Option<Instant>,
|
||||
|
||||
max_group_size: u32,
|
||||
// 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>,
|
||||
// Note: potentially representable as a client only component
|
||||
group_members: HashMap<Uid, group::Role>,
|
||||
@ -202,6 +206,7 @@ pub struct Client {
|
||||
state: State,
|
||||
|
||||
view_distance: Option<u32>,
|
||||
lod_distance: f32,
|
||||
// TODO: move into voxygen
|
||||
loaded_distance: f32,
|
||||
|
||||
@ -624,6 +629,9 @@ impl Client {
|
||||
available_recipes: HashMap::default(),
|
||||
chat_mode: ChatMode::default(),
|
||||
|
||||
lod_zones: HashMap::new(),
|
||||
lod_last_requested: None,
|
||||
|
||||
max_group_size,
|
||||
invite: None,
|
||||
group_leader: None,
|
||||
@ -650,6 +658,7 @@ impl Client {
|
||||
tick: 0,
|
||||
state,
|
||||
view_distance: None,
|
||||
lod_distance: 4.0,
|
||||
loaded_distance: 0.0,
|
||||
|
||||
pending_chunks: HashMap::new(),
|
||||
@ -769,7 +778,8 @@ impl Client {
|
||||
&mut self.in_game_stream
|
||||
},
|
||||
//Only in game, terrain
|
||||
ClientGeneral::TerrainChunkRequest { .. } => {
|
||||
ClientGeneral::TerrainChunkRequest { .. }
|
||||
| ClientGeneral::LodZoneRequest { .. } => {
|
||||
#[cfg(feature = "tracy")]
|
||||
{
|
||||
terrain = 1.0;
|
||||
@ -880,6 +890,11 @@ impl Client {
|
||||
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) {
|
||||
self.control_action(ControlAction::InventoryAction(InventoryAction::Use(slot)))
|
||||
}
|
||||
@ -994,6 +1009,8 @@ impl Client {
|
||||
&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
|
||||
/// any, that is required to do so.
|
||||
pub fn can_craft_recipe(&self, recipe: &str) -> (bool, Option<SpriteKind>) {
|
||||
@ -1709,6 +1726,34 @@ impl Client {
|
||||
let now = Instant::now();
|
||||
self.pending_chunks
|
||||
.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(())
|
||||
@ -2084,6 +2129,10 @@ impl Client {
|
||||
}
|
||||
self.pending_chunks.remove(&key);
|
||||
},
|
||||
ServerGeneral::LodZoneUpdate { key, zone } => {
|
||||
self.lod_zones.insert(key, zone);
|
||||
self.lod_last_requested = None;
|
||||
},
|
||||
ServerGeneral::TerrainBlockUpdates(blocks) => {
|
||||
if let Some(mut blocks) = blocks.decompress() {
|
||||
blocks.drain().for_each(|(pos, block)| {
|
||||
|
@ -10,6 +10,7 @@ lazy_static = "1.4.0"
|
||||
assets_manager = {version = "0.7", features = ["bincode", "ron", "json"]}
|
||||
ron = { version = "0.7", default-features = false }
|
||||
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"] }
|
||||
tracing = "0.1"
|
||||
|
||||
|
@ -191,6 +191,22 @@ impl Asset for DotVoxAsset {
|
||||
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
|
||||
pub fn find_root() -> Option<PathBuf> {
|
||||
std::env::current_dir().map_or(None, |path| {
|
||||
|
@ -83,6 +83,9 @@ pub enum ClientGeneral {
|
||||
TerrainChunkRequest {
|
||||
key: Vec2<i32>,
|
||||
},
|
||||
LodZoneRequest {
|
||||
key: Vec2<i32>,
|
||||
},
|
||||
//Always possible
|
||||
ChatMsg(String),
|
||||
Command(String, Vec<String>),
|
||||
@ -128,6 +131,7 @@ impl ClientMsg {
|
||||
| ClientGeneral::ExitInGame
|
||||
| ClientGeneral::PlayerPhysics { .. }
|
||||
| ClientGeneral::TerrainChunkRequest { .. }
|
||||
| ClientGeneral::LodZoneRequest { .. }
|
||||
| ClientGeneral::UnlockSkill(_)
|
||||
| ClientGeneral::RequestSiteInfo(_)
|
||||
| ClientGeneral::UnlockSkillGroup(_)
|
||||
|
@ -7,6 +7,7 @@ use common::{
|
||||
calendar::Calendar,
|
||||
character::{self, CharacterItem},
|
||||
comp::{self, invite::InviteKind, item::MaterialStatManifest},
|
||||
lod,
|
||||
outcome::Outcome,
|
||||
recipe::RecipeBook,
|
||||
resources::TimeOfDay,
|
||||
@ -169,6 +170,10 @@ pub enum ServerGeneral {
|
||||
key: Vec2<i32>,
|
||||
chunk: Result<SerializedTerrainChunk, ()>,
|
||||
},
|
||||
LodZoneUpdate {
|
||||
key: Vec2<i32>,
|
||||
zone: lod::Zone,
|
||||
},
|
||||
TerrainBlockUpdates(CompressedData<HashMap<Vec3<i32>, Block>>),
|
||||
// Always possible
|
||||
PlayerListUpdate(PlayerListUpdate),
|
||||
@ -293,6 +298,7 @@ impl ServerMsg {
|
||||
| ServerGeneral::ExitInGameSuccess
|
||||
| ServerGeneral::InventoryUpdate(_, _)
|
||||
| ServerGeneral::TerrainChunkUpdate { .. }
|
||||
| ServerGeneral::LodZoneUpdate { .. }
|
||||
| ServerGeneral::TerrainBlockUpdates(_)
|
||||
| ServerGeneral::SetViewDistance(_)
|
||||
| ServerGeneral::Outcomes(_)
|
||||
|
@ -49,6 +49,7 @@ pub mod figure;
|
||||
pub mod generation;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod grid;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod link;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod lod;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod lottery;
|
||||
#[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
|
||||
ServerGeneral::TerrainChunkUpdate { .. }
|
||||
| ServerGeneral::LodZoneUpdate { .. }
|
||||
| ServerGeneral::TerrainBlockUpdates(_) => {
|
||||
self.terrain_stream.lock().unwrap().send(g)
|
||||
},
|
||||
@ -191,6 +192,7 @@ impl Client {
|
||||
},
|
||||
//Ingame related, terrain
|
||||
ServerGeneral::TerrainChunkUpdate { .. }
|
||||
| ServerGeneral::LodZoneUpdate { .. }
|
||||
| ServerGeneral::TerrainBlockUpdates(_) => {
|
||||
PreparedMsg::new(5, &g, &self.terrain_stream_params)
|
||||
},
|
||||
|
@ -24,6 +24,7 @@ pub mod error;
|
||||
pub mod events;
|
||||
pub mod input;
|
||||
pub mod location;
|
||||
pub mod lod;
|
||||
pub mod login_provider;
|
||||
pub mod metrics;
|
||||
pub mod persistence;
|
||||
@ -449,6 +450,9 @@ impl Server {
|
||||
// Insert the world into the ECS (todo: Maybe not an Arc?)
|
||||
let world = Arc::new(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());
|
||||
|
||||
// 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::Spectate
|
||||
| ClientGeneral::TerrainChunkRequest { .. }
|
||||
| ClientGeneral::LodZoneRequest { .. }
|
||||
| ClientGeneral::ChatMsg(_)
|
||||
| ClientGeneral::Command(..)
|
||||
| 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::{
|
||||
comp::Pos,
|
||||
event::{EventBus, ServerEvent},
|
||||
@ -20,6 +22,7 @@ impl<'a> System<'a> for Sys {
|
||||
Entities<'a>,
|
||||
Read<'a, EventBus<ServerEvent>>,
|
||||
ReadExpect<'a, TerrainGrid>,
|
||||
ReadExpect<'a, Lod>,
|
||||
ReadExpect<'a, NetworkRequestMetrics>,
|
||||
Write<'a, Vec<ChunkRequest>>,
|
||||
ReadStorage<'a, Pos>,
|
||||
@ -37,6 +40,7 @@ impl<'a> System<'a> for Sys {
|
||||
entities,
|
||||
server_event_bus,
|
||||
terrain,
|
||||
lod,
|
||||
network_metrics,
|
||||
mut chunk_requests,
|
||||
positions,
|
||||
@ -101,6 +105,12 @@ impl<'a> System<'a> for Sys {
|
||||
network_metrics.chunks_request_dropped.inc();
|
||||
}
|
||||
},
|
||||
ClientGeneral::LodZoneRequest { key } => {
|
||||
client.send(ServerGeneral::LodZoneUpdate {
|
||||
key,
|
||||
zone: lod.zone(key).clone(),
|
||||
})?;
|
||||
},
|
||||
_ => {
|
||||
debug!(
|
||||
"Kicking possibly misbehaving client due to invalud terrain \
|
||||
|
@ -39,6 +39,9 @@ widget_ids! {
|
||||
vd_slider,
|
||||
vd_text,
|
||||
vd_value,
|
||||
ld_slider,
|
||||
ld_text,
|
||||
ld_value,
|
||||
lod_detail_slider,
|
||||
lod_detail_text,
|
||||
lod_detail_value,
|
||||
@ -280,8 +283,6 @@ impl<'a> Widget for Video<'a> {
|
||||
if let Some(new_val) = ImageSlider::discrete(
|
||||
self.global_state.settings.graphics.view_distance,
|
||||
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,
|
||||
self.imgs.slider_indicator,
|
||||
self.imgs.slider,
|
||||
@ -306,9 +307,44 @@ impl<'a> Widget for Video<'a> {
|
||||
.color(TEXT_COLOR)
|
||||
.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
|
||||
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_id(self.fonts.cyri.conrod_id)
|
||||
.color(TEXT_COLOR)
|
||||
@ -343,7 +379,7 @@ impl<'a> Widget for Video<'a> {
|
||||
|
||||
// Max 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)
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
@ -391,7 +427,7 @@ impl<'a> Widget for Video<'a> {
|
||||
|
||||
// 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)
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
|
@ -135,8 +135,9 @@ impl PlayState for CharSelectionState {
|
||||
{
|
||||
let mut c = self.client.borrow_mut();
|
||||
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_lod_distance(global_state.settings.graphics.lod_distance);
|
||||
}
|
||||
return PlayStateResult::Switch(Box::new(SessionState::new(
|
||||
global_state,
|
||||
|
@ -26,6 +26,7 @@ pub use self::{
|
||||
Locals as FigureLocals,
|
||||
},
|
||||
fluid::Vertex as FluidVertex,
|
||||
lod_object::{Instance as LodObjectInstance, Vertex as LodObjectVertex},
|
||||
lod_terrain::{LodData, Vertex as LodTerrainVertex},
|
||||
particle::{Instance as ParticleInstance, Vertex as ParticleVertex},
|
||||
postprocess::Locals as PostProcessLocals,
|
||||
@ -459,4 +460,8 @@ pub enum ExperimentalShader {
|
||||
BareMinimum,
|
||||
/// Lowers strength of the glow effect for lights near the camera.
|
||||
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 figure;
|
||||
pub mod fluid;
|
||||
pub mod lod_object;
|
||||
pub mod lod_terrain;
|
||||
pub mod particle;
|
||||
pub mod postprocess;
|
||||
|
@ -4,8 +4,9 @@ use super::{
|
||||
instances::Instances,
|
||||
model::{DynamicModel, Model, SubModel},
|
||||
pipelines::{
|
||||
blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, shadow, skybox,
|
||||
sprite, terrain, trail, ui, ColLights, GlobalsBindGroup, ShadowTexturesBindGroup,
|
||||
blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle, shadow,
|
||||
skybox, sprite, terrain, trail, ui, ColLights, GlobalsBindGroup,
|
||||
ShadowTexturesBindGroup,
|
||||
},
|
||||
},
|
||||
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> {
|
||||
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]
|
||||
pub struct FluidDrawer<'pass_ref, 'pass: 'pass_ref> {
|
||||
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
|
||||
|
@ -1,8 +1,8 @@
|
||||
use super::{
|
||||
super::{
|
||||
pipelines::{
|
||||
blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, postprocess, shadow,
|
||||
skybox, sprite, terrain, trail, ui,
|
||||
blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle,
|
||||
postprocess, shadow, skybox, sprite, terrain, trail, ui,
|
||||
},
|
||||
AaMode, BloomMode, CloudMode, FluidMode, LightingMode, PipelineModes, RenderError,
|
||||
ShadowMode,
|
||||
@ -28,6 +28,7 @@ pub struct Pipelines {
|
||||
// player_shadow: figure::FigurePipeline,
|
||||
pub skybox: skybox::SkyboxPipeline,
|
||||
pub sprite: sprite::SpritePipeline,
|
||||
pub lod_object: lod_object::LodObjectPipeline,
|
||||
pub terrain: terrain::TerrainPipeline,
|
||||
pub ui: ui::UiPipeline,
|
||||
pub blit: blit::BlitPipeline,
|
||||
@ -49,6 +50,7 @@ pub struct IngamePipelines {
|
||||
// player_shadow: figure::FigurePipeline,
|
||||
skybox: skybox::SkyboxPipeline,
|
||||
sprite: sprite::SpritePipeline,
|
||||
lod_object: lod_object::LodObjectPipeline,
|
||||
terrain: terrain::TerrainPipeline,
|
||||
}
|
||||
|
||||
@ -85,6 +87,7 @@ impl Pipelines {
|
||||
//player_shadow: ingame.player_shadow,
|
||||
skybox: ingame.skybox,
|
||||
sprite: ingame.sprite,
|
||||
lod_object: ingame.lod_object,
|
||||
terrain: ingame.terrain,
|
||||
ui: interface.ui,
|
||||
blit: interface.blit,
|
||||
@ -106,6 +109,8 @@ struct ShaderModules {
|
||||
fluid_frag: wgpu::ShaderModule,
|
||||
sprite_vert: wgpu::ShaderModule,
|
||||
sprite_frag: wgpu::ShaderModule,
|
||||
lod_object_vert: wgpu::ShaderModule,
|
||||
lod_object_frag: wgpu::ShaderModule,
|
||||
particle_vert: wgpu::ShaderModule,
|
||||
particle_frag: wgpu::ShaderModule,
|
||||
trail_vert: wgpu::ShaderModule,
|
||||
@ -293,6 +298,8 @@ impl ShaderModules {
|
||||
fluid_frag: create_shader(&selected_fluid_shader, ShaderKind::Fragment)?,
|
||||
sprite_vert: create_shader("sprite-vert", ShaderKind::Vertex)?,
|
||||
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_frag: create_shader("particle-frag", ShaderKind::Fragment)?,
|
||||
trail_vert: create_shader("trail-vert", ShaderKind::Vertex)?,
|
||||
@ -415,7 +422,7 @@ fn create_ingame_and_shadow_pipelines(
|
||||
needs: PipelineNeeds,
|
||||
pool: &rayon::ThreadPool,
|
||||
// TODO: Reduce the boilerplate in this file
|
||||
tasks: [Task; 15],
|
||||
tasks: [Task; 16],
|
||||
) -> IngameAndShadowPipelines {
|
||||
prof_span!(_guard, "create_ingame_and_shadow_pipelines");
|
||||
|
||||
@ -434,6 +441,7 @@ fn create_ingame_and_shadow_pipelines(
|
||||
terrain_task,
|
||||
fluid_task,
|
||||
sprite_task,
|
||||
lod_object_task,
|
||||
particle_task,
|
||||
trail_task,
|
||||
lod_terrain_task,
|
||||
@ -546,6 +554,21 @@ fn create_ingame_and_shadow_pipelines(
|
||||
"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
|
||||
let create_particle = || {
|
||||
particle_task.run(
|
||||
@ -732,6 +755,7 @@ fn create_ingame_and_shadow_pipelines(
|
||||
create_figure_directed_shadow,
|
||||
)
|
||||
};
|
||||
let j7 = create_lod_object;
|
||||
|
||||
// Ignore this
|
||||
let (
|
||||
@ -739,10 +763,13 @@ fn create_ingame_and_shadow_pipelines(
|
||||
((debug, (skybox, figure)), (terrain, (fluid, bloom))),
|
||||
((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(j1, j2), || pool.join(j3, j4)),
|
||||
|| pool.join(j5, j6),
|
||||
|| pool.join(|| pool.join(j5, j6), j7),
|
||||
);
|
||||
|
||||
IngameAndShadowPipelines {
|
||||
@ -758,6 +785,7 @@ fn create_ingame_and_shadow_pipelines(
|
||||
postprocess,
|
||||
skybox,
|
||||
sprite,
|
||||
lod_object,
|
||||
terrain,
|
||||
// player_shadow_pipeline,
|
||||
},
|
||||
|
@ -61,6 +61,8 @@ impl assets::Compound for Shaders {
|
||||
"fluid-frag.shiny",
|
||||
"sprite-vert",
|
||||
"sprite-frag",
|
||||
"lod-object-vert",
|
||||
"lod-object-frag",
|
||||
"particle-vert",
|
||||
"particle-frag",
|
||||
"trail-vert",
|
||||
|
@ -1,17 +1,41 @@
|
||||
use crate::{
|
||||
render::{
|
||||
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,
|
||||
};
|
||||
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::*;
|
||||
|
||||
// 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 {
|
||||
model: Option<(u32, Model<LodTerrainVertex>)>,
|
||||
data: LodData,
|
||||
|
||||
zone_objects: HashMap<Vec2<i32>, HashMap<lod::ObjectKind, ObjectGroup>>,
|
||||
object_data: HashMap<lod::ObjectKind, Model<LodObjectVertex>>,
|
||||
}
|
||||
|
||||
// TODO: Make constant when possible.
|
||||
@ -22,18 +46,31 @@ pub fn water_color() -> Rgba<f32> {
|
||||
|
||||
impl Lod {
|
||||
pub fn new(renderer: &mut Renderer, client: &Client, settings: &Settings) -> Self {
|
||||
let data = LodData::new(
|
||||
renderer,
|
||||
client.world_data().chunk_size().as_(),
|
||||
client.world_data().lod_base.raw(),
|
||||
client.world_data().lod_alt.raw(),
|
||||
client.world_data().lod_horizon.raw(),
|
||||
settings.graphics.lod_detail.max(100).min(2500),
|
||||
/* TODO: figure out how we want to do this without color borders?
|
||||
* water_color().into_array().into(), */
|
||||
);
|
||||
Self {
|
||||
model: None,
|
||||
data: LodData::new(
|
||||
renderer,
|
||||
client.world_data().chunk_size().as_(),
|
||||
client.world_data().lod_base.raw(),
|
||||
client.world_data().lod_alt.raw(),
|
||||
client.world_data().lod_horizon.raw(),
|
||||
settings.graphics.lod_detail.max(100).min(2500),
|
||||
/* TODO: figure out how we want to do this without color borders?
|
||||
* water_color().into_array().into(), */
|
||||
),
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
.model
|
||||
.as_ref()
|
||||
@ -58,12 +102,100 @@ impl Lod {
|
||||
.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>) {
|
||||
if let Some((_, model)) = self.model.as_ref() {
|
||||
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()
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// Maintain LoD.
|
||||
self.lod.maintain(renderer);
|
||||
self.lod.maintain(renderer, client, focus_pos, &self.camera);
|
||||
|
||||
// Maintain debug shapes
|
||||
self.debug.maintain(renderer);
|
||||
|
@ -69,6 +69,7 @@ pub enum Gameplay {
|
||||
#[derive(Clone)]
|
||||
pub enum Graphics {
|
||||
AdjustViewDistance(u32),
|
||||
AdjustLodDistance(u32),
|
||||
AdjustLodDetail(u32),
|
||||
AdjustSpriteRenderDistance(u32),
|
||||
AdjustFigureLoDRenderDistance(u32),
|
||||
@ -344,6 +345,14 @@ impl SettingsChange {
|
||||
|
||||
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) => {
|
||||
session_state.scene.lod.set_detail(lod_detail);
|
||||
|
||||
|
@ -30,6 +30,7 @@ impl fmt::Display for Fps {
|
||||
#[serde(default)]
|
||||
pub struct GraphicsSettings {
|
||||
pub view_distance: u32,
|
||||
pub lod_distance: u32,
|
||||
pub sprite_render_distance: u32,
|
||||
pub particles_enabled: bool,
|
||||
pub lossy_terrain_compression: bool,
|
||||
@ -51,6 +52,7 @@ impl Default for GraphicsSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
view_distance: 10,
|
||||
lod_distance: 200,
|
||||
sprite_render_distance: 100,
|
||||
particles_enabled: true,
|
||||
lossy_terrain_compression: false,
|
||||
|
@ -3,7 +3,7 @@ use crate::{
|
||||
block::block_from_structure,
|
||||
column::ColumnGen,
|
||||
util::{gen_cache::StructureGenCache, RandomPerm, Sampler, UnitChooser},
|
||||
Canvas,
|
||||
Canvas, ColumnSample,
|
||||
};
|
||||
use common::{
|
||||
assets::AssetHandle,
|
||||
@ -33,6 +33,23 @@ static MODEL_RAND: RandomPerm = RandomPerm::new(0xDB21C052);
|
||||
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 {
|
||||
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(
|
||||
canvas: &mut Canvas,
|
||||
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))?;
|
||||
|
||||
// Ensure that it's valid to place a *thing* here
|
||||
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 {
|
||||
if !tree_valid_at(&col, seed) {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
105
world/src/lib.rs
105
world/src/lib.rs
@ -51,6 +51,7 @@ use common::{
|
||||
assets,
|
||||
calendar::Calendar,
|
||||
generation::{ChunkSupplement, EntityInfo},
|
||||
lod,
|
||||
resources::TimeOfDay,
|
||||
terrain::{
|
||||
Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize, TerrainGrid,
|
||||
@ -60,6 +61,7 @@ use common::{
|
||||
use common_net::msg::{world_msg, WorldMapMsg};
|
||||
use rand::{prelude::*, Rng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use rayon::iter::ParallelIterator;
|
||||
use serde::Deserialize;
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
@ -463,4 +465,107 @@ impl World {
|
||||
|
||||
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,36 +2134,40 @@ impl WorldSim {
|
||||
/// them spawning).
|
||||
pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
|
||||
// Deterministic based on wpos
|
||||
let normal_trees =
|
||||
self.gen_ctx
|
||||
.structure_gen
|
||||
.get(wpos)
|
||||
.into_iter()
|
||||
.filter_map(move |(wpos, seed)| {
|
||||
let lottery = self.make_forest_lottery(wpos);
|
||||
Some(TreeAttr {
|
||||
pos: wpos,
|
||||
seed,
|
||||
scale: 1.0,
|
||||
forest_kind: *lottery.choose_seeded(seed).as_ref()?,
|
||||
inhabited: false,
|
||||
})
|
||||
});
|
||||
self.gen_ctx
|
||||
.structure_gen
|
||||
.get(wpos)
|
||||
.into_iter()
|
||||
.filter_map(move |(wpos, seed)| {
|
||||
let lottery = self.make_forest_lottery(wpos);
|
||||
Some(TreeAttr {
|
||||
pos: wpos,
|
||||
seed,
|
||||
scale: 1.0,
|
||||
forest_kind: *lottery.choose_seeded(seed).as_ref()?,
|
||||
inhabited: false,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// // For testing
|
||||
// let giant_trees =
|
||||
// std::array::IntoIter::new(self.gen_ctx.big_structure_gen.get(wpos))
|
||||
// // Don't even consider trees if we aren't close
|
||||
// .filter(move |(pos, _)| pos.distance_squared(wpos) < 512i32.pow(2))
|
||||
// .map(move |(pos, seed)| TreeAttr {
|
||||
// pos,
|
||||
// seed,
|
||||
// scale: 5.0,
|
||||
// forest_kind: ForestKind::Giant,
|
||||
// inhabited: (seed / 13) % 2 == 0,
|
||||
// });
|
||||
|
||||
normal_trees //.chain(giant_trees)
|
||||
pub fn get_area_trees(
|
||||
&self,
|
||||
wpos_min: Vec2<i32>,
|
||||
wpos_max: Vec2<i32>,
|
||||
) -> impl ParallelIterator<Item = TreeAttr> + '_ {
|
||||
self.gen_ctx
|
||||
.structure_gen
|
||||
.par_iter(wpos_min, wpos_max)
|
||||
.filter_map(move |(wpos, seed)| {
|
||||
let lottery = self.make_forest_lottery(wpos);
|
||||
Some(TreeAttr {
|
||||
pos: wpos,
|
||||
seed,
|
||||
scale: 1.0,
|
||||
forest_kind: *lottery.choose_seeded(seed).as_ref()?,
|
||||
inhabited: false,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user