From fcb7011cde1c19775cb822d41d0ff3d20bd8f43c Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 22 Feb 2023 09:52:40 +0100 Subject: [PATCH] sprites on volumes --- assets/common/manifests/ship_manifest.ron | 28 +- assets/common/voxel/air_balloon/structure.vox | 4 +- .../common/voxel/airship_human/structure.vox | 4 +- assets/voxygen/shaders/fluid-frag/cheap.glsl | 7 +- assets/voxygen/shaders/fluid-frag/shiny.glsl | 7 +- assets/voxygen/shaders/fluid-vert.glsl | 18 +- .../shaders/light-shadows-directed-vert.glsl | 21 +- .../voxygen/shaders/light-shadows-vert.glsl | 21 +- .../shaders/point-light-shadows-vert.glsl | 21 +- .../shaders/rain-occlusion-directed-vert.glsl | 15 +- assets/voxygen/shaders/sprite-vert.glsl | 24 +- assets/voxygen/shaders/terrain-frag.glsl | 10 +- assets/voxygen/shaders/terrain-vert.glsl | 14 +- common/src/comp/body/ship.rs | 63 +- common/src/figure/mod.rs | 23 +- common/src/terrain/block.rs | 12 +- common/src/terrain/structure.rs | 86 ++- common/src/vol.rs | 2 + voxygen/src/mesh/segment.rs | 118 +++- voxygen/src/mesh/terrain.rs | 4 +- voxygen/src/render/pipelines/terrain.rs | 25 +- voxygen/src/render/renderer.rs | 7 +- voxygen/src/render/renderer/drawer.rs | 6 +- voxygen/src/scene/figure/cache.rs | 479 ++++++++++++- voxygen/src/scene/figure/load.rs | 44 +- voxygen/src/scene/figure/mod.rs | 662 ++++++++++++------ voxygen/src/scene/figure/volume.rs | 22 +- voxygen/src/scene/lod.rs | 4 +- voxygen/src/scene/mod.rs | 20 +- voxygen/src/scene/particle.rs | 8 +- voxygen/src/scene/simple.rs | 18 +- voxygen/src/scene/terrain.rs | 227 +++--- voxygen/src/scene/terrain/watcher.rs | 17 +- 33 files changed, 1510 insertions(+), 531 deletions(-) diff --git a/assets/common/manifests/ship_manifest.ron b/assets/common/manifests/ship_manifest.ron index 390f1cc11f..95e688fd7d 100644 --- a/assets/common/manifests/ship_manifest.ron +++ b/assets/common/manifests/ship_manifest.ron @@ -1,93 +1,81 @@ ({ DefaultAirship: ( bone0: ( - //offset: (3.0, 7.0, 1.0), - //offset: (-20.75, -34.75, 1.25), - //offset: (0.0, 0.0, 0.0), offset: (-17.5, -39.0, 1.0), - //phys_offset: (0.25, 0.25, 0.25), - phys_offset: (0.0, 0.0, 0.0), central: ("airship_human.structure"), ), bone1: ( offset: (-8.5, -2.0, -8.5), - phys_offset: (0.0, 0.0, 0.0), central: ("airship_human.propeller-l"), ), bone2: ( offset: (-8.5, -2.0, -8.5), - phys_offset: (0.0, 0.0, 0.0), central: ("airship_human.propeller-r"), ), bone3: ( offset: (-1.5, -11.0, -5.5), - phys_offset: (0.0, 0.0, 0.0), central: ("airship_human.rudder"), ), + + custom_indices: { + 1: Air(Door, 4), + }, ), AirBalloon: ( bone0: ( offset: (-14.5, -16.0, 0.0), - phys_offset: (0.0, 0.0, 0.0), central: ("air_balloon.structure"), ), bone1: ( offset: (0.0, 0.0, 0.0), - phys_offset: (0.0, 0.0, 0.0), central: ("empty"), ), bone2: ( offset: (0.0, 0.0, 0.0), - phys_offset: (0.0, 0.0, 0.0), central: ("empty"), ), bone3: ( offset: (-1.5, -6.0, -5.0), - phys_offset: (0.0, 0.0, 0.0), central: ("air_balloon.rudder"), ), + + custom_indices: { + 1: Air(ChairSingle, 4), + }, ), SailBoat: ( bone0: ( offset: (-6.5, -15.5, 0.0), - phys_offset: (0.0, 0.0, 0.0), central: ("sail_boat.structure"), ), bone1: ( offset: (0.0, 0.0, 0.0), - phys_offset: (0.0, 0.0, 0.0), central: ("empty"), ), bone2: ( offset: (0.0, 0.0, 0.0), - phys_offset: (0.0, 0.0, 0.0), central: ("empty"), ), bone3: ( offset: (-1.5, -6.0, -5.0), - phys_offset: (0.0, 0.0, 0.0), central: ("empty"), ), ), Galleon: ( bone0: ( offset: (-16, -16, -3.0), - phys_offset: (0.0, 0.0, 0.0), central: ("galleon.structure"), ), bone1: ( offset: (0.0, 0.0, 0.0), - phys_offset: (0.0, 0.0, 0.0), central: ("empty"), ), bone2: ( offset: (0.0, 0.0, 0.0), - phys_offset: (0.0, 0.0, 0.0), central: ("empty"), ), bone3: ( offset: (0.0, 0.0, 0.0), - phys_offset: (0.0, 0.0, 0.0), central: ("empty"), ), ), diff --git a/assets/common/voxel/air_balloon/structure.vox b/assets/common/voxel/air_balloon/structure.vox index c6d6faaadb..3e4fb558cf 100644 --- a/assets/common/voxel/air_balloon/structure.vox +++ b/assets/common/voxel/air_balloon/structure.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff86a7d110f72d9d8c40504f95efe1ea0e58c8d1b68fc7560ecccd119d80a1a8 -size 57016 +oid sha256:f4b706d04415212e276e97d62a6e6f902aae17d8ddbe00abde3c3fd6060fd843 +size 78495 diff --git a/assets/common/voxel/airship_human/structure.vox b/assets/common/voxel/airship_human/structure.vox index b4fa16fd1c..f567305bd4 100644 --- a/assets/common/voxel/airship_human/structure.vox +++ b/assets/common/voxel/airship_human/structure.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6704005c60327e0b288bff92cca7630b2b48e5167280a4f36a2072854d7204f1 -size 77892 +oid sha256:cee69dda42513edc342129fe9a3d03a120f71b35000f0c8ce602888b5ac33dd5 +size 99391 diff --git a/assets/voxygen/shaders/fluid-frag/cheap.glsl b/assets/voxygen/shaders/fluid-frag/cheap.glsl index cbb78f00fd..cd3744566a 100644 --- a/assets/voxygen/shaders/fluid-frag/cheap.glsl +++ b/assets/voxygen/shaders/fluid-frag/cheap.glsl @@ -40,9 +40,12 @@ layout(location = 2) in vec2 f_vel; layout(std140, set = 2, binding = 0) uniform u_locals { - vec3 model_offs; - float load_time; + vec4 model_mat0; + vec4 model_mat1; + vec4 model_mat2; + vec4 model_mat3; ivec4 atlas_offs; + float load_time; }; layout(location = 0) out vec4 tgt_color; diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl index e0d79ad5cd..1fae3e675e 100644 --- a/assets/voxygen/shaders/fluid-frag/shiny.glsl +++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl @@ -42,9 +42,12 @@ layout(location = 2) in vec2 f_vel; layout(std140, set = 2, binding = 0) uniform u_locals { - vec3 model_offs; - float load_time; + vec4 model_mat0; + vec4 model_mat1; + vec4 model_mat2; + vec4 model_mat3; ivec4 atlas_offs; + float load_time; }; layout(location = 0) out vec4 tgt_color; diff --git a/assets/voxygen/shaders/fluid-vert.glsl b/assets/voxygen/shaders/fluid-vert.glsl index 16d3a12dfa..2ddc0a0ebc 100644 --- a/assets/voxygen/shaders/fluid-vert.glsl +++ b/assets/voxygen/shaders/fluid-vert.glsl @@ -26,9 +26,12 @@ layout(location = 1) in uint v_vel; layout(std140, set = 2, binding = 0) uniform u_locals { - vec3 model_offs; - float load_time; + vec4 model_mat0; + vec4 model_mat1; + vec4 model_mat2; + vec4 model_mat3; ivec4 atlas_offs; + float load_time; }; // struct ShadowLocals { @@ -51,7 +54,14 @@ layout(location = 2) out vec2 f_vel; const float EXTRA_NEG_Z = 65536.0/*65536.1*/; void main() { - f_pos = vec3(v_pos_norm & 0x3Fu, (v_pos_norm >> 6) & 0x3Fu, float((v_pos_norm >> 12) & 0x1FFFFu) - EXTRA_NEG_Z) + model_offs - focus_off.xyz; + mat4 model_mat; + model_mat[0] = model_mat0; + model_mat[1] = model_mat1; + model_mat[2] = model_mat2; + model_mat[3] = model_mat3; + vec3 rel_pos = vec3(v_pos_norm & 0x3Fu, (v_pos_norm >> 6) & 0x3Fu, float((v_pos_norm >> 12) & 0x1FFFFu) - EXTRA_NEG_Z); + f_pos = (model_mat * vec4(rel_pos, 1.0)).xyz - focus_off.xyz; + f_vel = vec2( (float(v_vel & 0xFFFFu) - 32768.0) / 1000.0, (float((v_vel >> 16u) & 0xFFFFu) - 32768.0) / 1000.0 @@ -68,7 +78,7 @@ void main() { #endif #endif - float pull_down = pow(distance(focus_pos.xy, f_pos.xy) / (view_distance.x * 0.95), 20.0) * 0.7; + // 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 diff --git a/assets/voxygen/shaders/light-shadows-directed-vert.glsl b/assets/voxygen/shaders/light-shadows-directed-vert.glsl index 38486aa808..890210bef5 100644 --- a/assets/voxygen/shaders/light-shadows-directed-vert.glsl +++ b/assets/voxygen/shaders/light-shadows-directed-vert.glsl @@ -44,9 +44,12 @@ layout(location = 0) in uint v_pos_norm; // Light projection matrices. layout (std140, set = 1, binding = 0) uniform u_locals { - vec3 model_offs; - float load_time; + vec4 model_mat0; + vec4 model_mat1; + vec4 model_mat2; + vec4 model_mat3; ivec4 atlas_offs; + float load_time; }; // out vec4 shadowMapCoord; @@ -54,12 +57,16 @@ uniform u_locals { const float EXTRA_NEG_Z = 32768.0; void main() { - vec3 f_chunk_pos = vec3(v_pos_norm & 0x3Fu, (v_pos_norm >> 6) & 0x3Fu, float((v_pos_norm >> 12) & 0xFFFFu) - EXTRA_NEG_Z); - vec3 f_pos = f_chunk_pos + (model_offs - focus_off.xyz); - // f_pos = v_pos; - // vec3 f_pos = f_chunk_pos + model_offs; + mat4 model_mat; + model_mat[0] = model_mat0; + model_mat[1] = model_mat1; + model_mat[2] = model_mat2; + model_mat[3] = model_mat3; + + vec3 f_chunk_pos = vec3(v_pos_norm & 0x3Fu, (v_pos_norm >> 6) & 0x3Fu, float((v_pos_norm >> 12) & 0xFFFFu) - EXTRA_NEG_Z); + vec3 f_pos = (vec4(f_chunk_pos, 1.0) * model_mat).xyz - focus_off.xyz; + // f_pos = v_pos; - // gl_Position = v_pos + vec4(model_offs, 0.0); gl_Position = /*all_mat * */shadowMatrices * vec4(f_pos/*, 1.0*/, /*float(((f_pos_norm >> 29) & 0x7u) ^ 0x1)*//*uintBitsToFloat(v_pos_norm)*/1.0); // gl_Position.z = -gl_Position.z; // gl_Position.z = clamp(gl_Position.z, -abs(gl_Position.w), abs(gl_Position.w)); diff --git a/assets/voxygen/shaders/light-shadows-vert.glsl b/assets/voxygen/shaders/light-shadows-vert.glsl index dd98eeb8e9..b75a44397b 100644 --- a/assets/voxygen/shaders/light-shadows-vert.glsl +++ b/assets/voxygen/shaders/light-shadows-vert.glsl @@ -33,9 +33,12 @@ layout(location = 1) in uint v_pos_norm; // Light projection matrices. layout (std140, set = 1, binding = 0) uniform u_locals { - vec3 model_offs; - float load_time; + vec4 model_mat0; + vec4 model_mat1; + vec4 model_mat2; + vec4 model_mat3; ivec4 atlas_offs; + float load_time; }; // out vec4 shadowMapCoord; @@ -43,12 +46,16 @@ uniform u_locals { const int EXTRA_NEG_Z = 32768; void main() { - vec3 f_chunk_pos = vec3(ivec3((uvec3(v_pos_norm) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0xFFFFu)) - ivec3(0, 0, EXTRA_NEG_Z)); - vec3 f_pos = f_chunk_pos + model_offs - focus_off.xyz; - // f_pos = v_pos; - // vec3 f_pos = f_chunk_pos + model_offs; + mat4 model_mat; + model_mat[0] = model_mat0; + model_mat[1] = model_mat1; + model_mat[2] = model_mat2; + model_mat[3] = model_mat3; + + vec3 f_chunk_pos = vec3(ivec3((uvec3(v_pos_norm) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0xFFFFu)) - ivec3(0, 0, EXTRA_NEG_Z)); + vec3 f_pos = (vec4(f_chunk_pos, 1.0) * model_mat).xyz - focus_off.xyz; + // f_pos = v_pos; - // gl_Position = v_pos + vec4(model_offs, 0.0); gl_Position = /*all_mat * */vec4(f_pos/*, 1.0*/, /*float(((f_pos_norm >> 29) & 0x7u) ^ 0x1)*//*uintBitsToFloat(v_pos_norm)*/1.0); // shadowMapCoord = lights[gl_InstanceID].light_pos * gl_Vertex; // vec4(v_pos, 0.0, 1.0); diff --git a/assets/voxygen/shaders/point-light-shadows-vert.glsl b/assets/voxygen/shaders/point-light-shadows-vert.glsl index 705f2efe8b..7489e6196d 100644 --- a/assets/voxygen/shaders/point-light-shadows-vert.glsl +++ b/assets/voxygen/shaders/point-light-shadows-vert.glsl @@ -34,9 +34,12 @@ layout(location = 0) in uint v_pos_norm; // Light projection matrices. layout (std140, set = 1, binding = 0) uniform u_locals { - vec3 model_offs; - float load_time; + vec4 model_mat0; + vec4 model_mat1; + vec4 model_mat2; + vec4 model_mat3; ivec4 atlas_offs; + float load_time; }; // out vec4 shadowMapCoord; @@ -48,12 +51,16 @@ layout( push_constant ) uniform PointLightMatrix { }; void main() { - vec3 f_chunk_pos = vec3(v_pos_norm & 0x3Fu, (v_pos_norm >> 6) & 0x3Fu, float((v_pos_norm >> 12) & 0xFFFFu) - EXTRA_NEG_Z); - vec3 f_pos = f_chunk_pos + model_offs - focus_off.xyz; - // f_pos = v_pos; - // vec3 f_pos = f_chunk_pos + model_offs; + mat4 model_mat; + model_mat[0] = model_mat0; + model_mat[1] = model_mat1; + model_mat[2] = model_mat2; + model_mat[3] = model_mat3; + + vec3 f_chunk_pos = vec3(v_pos_norm & 0x3Fu, (v_pos_norm >> 6) & 0x3Fu, float((v_pos_norm >> 12) & 0xFFFFu) - EXTRA_NEG_Z); + vec3 f_pos = (vec4(f_chunk_pos, 1.0) * model_mat).xyz - focus_off.xyz; + // f_pos = v_pos; - // gl_Position = v_pos + vec4(model_offs, 0.0); // gl_Position = /*all_mat * */vec4(f_pos/*, 1.0*/, /*float(((f_pos_norm >> 29) & 0x7u) ^ 0x1)*//*uintBitsToFloat(v_pos_norm)*/1.0); // shadowMapCoord = lights[gl_InstanceID].light_pos * gl_Vertex; // vec4(v_pos, 0.0, 1.0); diff --git a/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl b/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl index eab9446343..285a745146 100644 --- a/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl +++ b/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl @@ -48,9 +48,12 @@ layout(location = 0) in uint v_pos_norm; // Light projection matrices. layout (std140, set = 1, binding = 0) uniform u_locals { - vec3 model_offs; - float load_time; + vec4 model_mat0; + vec4 model_mat1; + vec4 model_mat2; + vec4 model_mat3; ivec4 atlas_offs; + float load_time; }; // out vec4 shadowMapCoord; @@ -58,8 +61,14 @@ uniform u_locals { const float EXTRA_NEG_Z = 32768.0; void main() { + mat4 model_mat; + model_mat[0] = model_mat0; + model_mat[1] = model_mat1; + model_mat[2] = model_mat2; + model_mat[3] = model_mat3; + vec3 f_chunk_pos = vec3(v_pos_norm & 0x3Fu, (v_pos_norm >> 6) & 0x3Fu, float((v_pos_norm >> 12) & 0xFFFFu) - EXTRA_NEG_Z); - vec3 f_pos = f_chunk_pos + (model_offs - focus_off.xyz); + vec3 f_pos = (vec4(f_chunk_pos, 1.0) * model_mat).xyz - focus_off.xyz; gl_Position = rain_occlusion_matrices * vec4(f_pos, 1.0); } diff --git a/assets/voxygen/shaders/sprite-vert.glsl b/assets/voxygen/shaders/sprite-vert.glsl index 94ea351f11..9b2679ddb5 100644 --- a/assets/voxygen/shaders/sprite-vert.glsl +++ b/assets/voxygen/shaders/sprite-vert.glsl @@ -37,9 +37,12 @@ layout(set = 0, binding = 15) restrict readonly buffer sprite_verts { layout (std140, set = 3, binding = 0) uniform u_terrain_locals { - vec3 model_offs; - float load_time; + vec4 model_mat0; + vec4 model_mat1; + vec4 model_mat2; + vec4 model_mat3; ivec4 atlas_offs; + float load_time; }; // TODO: consider grouping into vec4's @@ -89,15 +92,23 @@ float wind_wave(float off, float scaling, float speed, float strength) { } void main() { + mat4 model_mat; + model_mat[0] = model_mat0; + model_mat[1] = model_mat1; + model_mat[2] = model_mat2; + model_mat[3] = model_mat3; + // Matrix to transform this sprite instance from model space to chunk space mat4 inst_mat; inst_mat[0] = inst_mat0; inst_mat[1] = inst_mat1; inst_mat[2] = inst_mat2; - inst_mat[3] = inst_mat3; + inst_mat[3] = inst_mat3;// + vec4(-14.5, -16.5, 0.0, 0.0); + + inst_mat = model_mat * inst_mat; // Worldpos of the chunk that this sprite is in - vec3 chunk_offs = model_offs - focus_off.xyz; + vec3 chunk_offs = -focus_off.xyz; f_inst_light = vec2(inst_light, inst_glow); @@ -116,15 +127,14 @@ void main() { // Position of the sprite block in the chunk // Used for highlighting the selected sprite, and for opening doors - vec3 inst_chunk_pos = vec3(inst_pos_ori_door & 0x3Fu, (inst_pos_ori_door >> 6) & 0x3Fu, float((inst_pos_ori_door >> 12) & 0xFFFFu) - EXTRA_NEG_Z); - vec3 sprite_pos = inst_chunk_pos + chunk_offs; + vec3 sprite_pos = inst_mat[3].xyz + chunk_offs; float sprite_ori = (inst_pos_ori_door >> 29) & 0x7u; #ifndef EXPERIMENTAL_BAREMINIMUM if((inst_pos_ori_door & (1 << 28)) != 0) { const float MIN_OPEN_DIST = 0.2; const float MAX_OPEN_DIST = 1.5; - float min_entity_dist = nearest_entity(sprite_pos + 0.5, 1.0).w; + float min_entity_dist = nearest_entity(sprite_pos, 1.0).w; if (min_entity_dist < MAX_OPEN_DIST) { float flip = sprite_ori <= 3 ? 1.0 : -1.0; diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 5e92ca2c10..d4b309fd2d 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -52,9 +52,12 @@ uniform sampler s_col_light; layout (std140, set = 3, binding = 0) uniform u_locals { - vec3 model_offs; - float load_time; + vec4 model_mat0; + vec4 model_mat1; + vec4 model_mat2; + vec4 model_mat3; ivec4 atlas_offs; + float load_time; }; layout(location = 0) out vec4 tgt_color; @@ -452,7 +455,8 @@ void main() { // light_reflection_factorplight_reflection_factor // vec3 surf_color = illuminate(srgb_to_linear(f_col), light, diffuse_light, ambient_light); - vec3 f_chunk_pos = f_pos - (model_offs - focus_off.xyz); + + vec3 f_chunk_pos = f_pos - (model_mat3.xyz - focus_off.xyz); #ifdef EXPERIMENTAL_NONOISE float noise = 0.0; #else diff --git a/assets/voxygen/shaders/terrain-vert.glsl b/assets/voxygen/shaders/terrain-vert.glsl index 55eea74c1b..f4414230ab 100644 --- a/assets/voxygen/shaders/terrain-vert.glsl +++ b/assets/voxygen/shaders/terrain-vert.glsl @@ -30,10 +30,13 @@ layout(location = 1) in uint v_atlas_pos; layout (std140, set = 3, binding = 0) uniform u_locals { - vec3 model_offs; - float load_time; + vec4 model_mat0; + vec4 model_mat1; + vec4 model_mat2; + vec4 model_mat3; // TODO: consider whether these need to be signed ivec4 atlas_offs; + float load_time; }; //struct ShadowLocals { @@ -70,10 +73,15 @@ layout(location = 2) flat out float f_load_time; const float EXTRA_NEG_Z = 32768.0; void main() { + mat4 model_mat; + model_mat[0] = model_mat0; + model_mat[1] = model_mat1; + model_mat[2] = model_mat2; + model_mat[3] = model_mat3; // over it (if this vertex to see if it intersects. // f_chunk_pos = vec3(ivec3((uvec3(v_pos_norm) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0xFFFFu)) - ivec3(0, 0, EXTRA_NEG_Z)); vec3 f_chunk_pos = vec3(v_pos_norm & 0x3Fu, (v_pos_norm >> 6) & 0x3Fu, float((v_pos_norm >> 12) & 0xFFFFu) - EXTRA_NEG_Z); - f_pos = f_chunk_pos + model_offs - focus_off.xyz; + f_pos = (model_mat * vec4(f_chunk_pos, 1.0)).xyz - focus_off.xyz; f_load_time = load_time; diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs index a1618d7958..0b61755aec 100644 --- a/common/src/comp/body/ship.rs +++ b/common/src/comp/body/ship.rs @@ -146,17 +146,17 @@ pub const AIRSHIP_SCALE: f32 = 11.0; pub mod figuredata { use crate::{ assets::{self, AssetExt, AssetHandle, DotVoxAsset, Ron}, - figure::cell::Cell, + figure::TerrainSegment, terrain::{ block::{Block, BlockKind}, sprite::SpriteKind, + structure::load_base_structure, }, - volumes::dyna::{ColumnAccess, Dyna}, }; use hashbrown::HashMap; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; - use vek::Vec3; + use vek::{Rgb, Vec3}; #[derive(Deserialize)] pub struct VoxSimple(pub String); @@ -164,18 +164,43 @@ pub mod figuredata { #[derive(Deserialize)] pub struct ShipCentralSpec(pub HashMap); + #[derive(Deserialize)] + pub enum DeBlock { + Block(BlockKind), + Air(SpriteKind, #[serde(default)] u8), + Water(SpriteKind, #[serde(default)] u8), + } + + impl DeBlock { + fn to_block(&self, color: Rgb) -> Block { + match self { + &DeBlock::Block(block) => Block::new(block, color), + &DeBlock::Air(sprite, ori) => { + let block = Block::new(BlockKind::Air, color).with_sprite(sprite); + block.with_ori(ori).unwrap_or(block) + }, + &DeBlock::Water(sprite, ori) => { + let block = Block::new(BlockKind::Water, color).with_sprite(sprite); + block.with_ori(ori).unwrap_or(block) + }, + } + } + } + #[derive(Deserialize)] pub struct SidedShipCentralVoxSpec { pub bone0: ShipCentralSubSpec, pub bone1: ShipCentralSubSpec, pub bone2: ShipCentralSubSpec, pub bone3: ShipCentralSubSpec, + + #[serde(default)] + pub custom_indices: HashMap, } #[derive(Deserialize)] pub struct ShipCentralSubSpec { pub offset: [f32; 3], - pub phys_offset: [f32; 3], pub central: VoxSimple, #[serde(default)] pub model_index: u32, @@ -190,7 +215,7 @@ pub mod figuredata { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct VoxelCollider { - pub(super) dyna: Dyna, + pub(super) dyna: TerrainSegment, pub translation: Vec3, /// This value should be incremented every time the volume is mutated /// and can be used to keep track of volume changes. @@ -200,13 +225,13 @@ pub mod figuredata { impl VoxelCollider { pub fn from_fn) -> Block>(sz: Vec3, f: F) -> Self { Self { - dyna: Dyna::from_fn(sz, (), f), + dyna: TerrainSegment::from_fn(sz, (), f), translation: -sz.map(|e| e as f32) / 2.0, mut_count: 0, } } - pub fn volume(&self) -> &Dyna { &self.dyna } + pub fn volume(&self) -> &TerrainSegment { &self.dyna } } impl assets::Compound for ShipSpec { @@ -218,27 +243,29 @@ pub mod figuredata { AssetExt::load("common.manifests.ship_manifest")?; let mut colliders = HashMap::new(); for (_, spec) in (manifest.read().0).0.iter() { - for bone in [&spec.bone0, &spec.bone1, &spec.bone2].iter() { + for bone in [&spec.bone0, &spec.bone1, &spec.bone2, &spec.bone3].iter() { // TODO: Currently both client and server load models and manifests from // "common.voxel.". In order to support CSG procedural airships, we probably // need to load them in the server and sync them as an ECS resource. let vox = cache.load::(&["common.voxel.", &bone.central.0].concat())?; - let dyna = Dyna::::from_vox( - &vox.read().0, - false, - bone.model_index as usize, - ); - let dyna = dyna.map_into(|cell| { - if let Some(rgb) = cell.get_color() { - Block::new(BlockKind::Misc, rgb) + + let base_structure = load_base_structure(&vox.read().0, |col| col); + let dyna = base_structure.vol.map_into(|cell| { + if let Some(i) = cell { + let color = base_structure.palette[u8::from(i) as usize]; + if let Some(block) = spec.custom_indices.get(&i.into()) { + block.to_block(color) + } else { + Block::new(BlockKind::Misc, color) + } } else { - Block::air(SpriteKind::Empty) + Block::empty() } }); let collider = VoxelCollider { dyna, - translation: Vec3::from(bone.offset) + Vec3::from(bone.phys_offset), + translation: Vec3::from(bone.offset), mut_count: 0, }; colliders.insert(bone.central.0.clone(), collider); diff --git a/common/src/figure/mod.rs b/common/src/figure/mod.rs index f23035e615..f9de47f922 100644 --- a/common/src/figure/mod.rs +++ b/common/src/figure/mod.rs @@ -10,11 +10,32 @@ pub use self::{ use crate::{ vol::{IntoFullPosIterator, IntoFullVolIterator, ReadVol, SizedVol, Vox, WriteVol}, - volumes::dyna::Dyna, + volumes::dyna::Dyna, terrain::{Block, BlockKind}, }; use dot_vox::DotVoxData; use vek::*; +pub type TerrainSegment = Dyna; + +impl From for TerrainSegment { + fn from(value: Segment) -> Self { + TerrainSegment::from_fn(value.sz, (), |pos| { + match value.get(pos) { + Err(_) | Ok(Cell::Empty) => Block::empty(), + Ok(cell) => { + if cell.is_hollow() { + Block::empty() + } else if cell.is_glowy() { + Block::new(BlockKind::GlowingRock, cell.get_color().unwrap()) + } else { + Block::new(BlockKind::Misc, cell.get_color().unwrap()) + } + } + } + }) + } +} + /// A type representing a volume that may be part of an animated figure. /// /// Figures are used to represent things like characters, NPCs, mobs, etc. diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 7fadfcea64..79e5fc3139 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -3,7 +3,7 @@ use crate::{ comp::{fluid_dynamics::LiquidKind, tool::ToolKind}, consts::FRIC_GROUND, lottery::LootSpec, - make_case_elim, rtsim, + make_case_elim, rtsim, vol::Vox, }; use num_derive::FromPrimitive; use num_traits::FromPrimitive; @@ -117,6 +117,16 @@ pub struct Block { attr: [u8; 3], } +impl Vox for Block { + fn empty() -> Self { + Block::empty() + } + + fn is_empty(&self) -> bool { + self.is_air() && self.get_sprite().is_none() + } +} + impl Deref for Block { type Target = BlockKind; diff --git a/common/src/terrain/structure.rs b/common/src/terrain/structure.rs index e2ad7b8f84..3e74103fb1 100644 --- a/common/src/terrain/structure.rs +++ b/common/src/terrain/structure.rs @@ -5,6 +5,7 @@ use crate::{ vol::{BaseVol, ReadVol, SizedVol, WriteVol}, volumes::dyna::{Dyna, DynaError}, }; +use dot_vox::DotVoxData; use hashbrown::HashMap; use serde::Deserialize; use std::{num::NonZeroU8, sync::Arc}; @@ -44,6 +45,12 @@ make_case_elim!( } ); +impl Default for StructureBlock { + fn default() -> Self { + StructureBlock::None + } +} + #[derive(Debug)] pub enum StructureError { OutOfBounds, @@ -52,14 +59,14 @@ pub enum StructureError { #[derive(Clone, Debug)] pub struct Structure { center: Vec3, - base: Arc, + base: Arc>, custom_indices: [Option; 256], } #[derive(Debug)] -struct BaseStructure { - vol: Dyna, ()>, - palette: [StructureBlock; 256], +pub(crate) struct BaseStructure { + pub(crate) vol: Dyna, ()>, + pub(crate) palette: [B; 256], } pub struct StructuresGroup(Vec); @@ -79,7 +86,7 @@ impl assets::Compound for StructuresGroup { .0 .iter() .map(|sp| { - let base = cache.load::>(&sp.specifier)?.cloned(); + let base = cache.load::>>(&sp.specifier)?.cloned(); Ok(Structure { center: Vec3::from(sp.center), base, @@ -138,43 +145,46 @@ impl ReadVol for Structure { } } -impl assets::Compound for BaseStructure { +pub(crate) fn load_base_structure(dot_vox_data: &DotVoxData, mut to_block: impl FnMut(Rgb) -> B) -> BaseStructure { + let mut palette = std::array::from_fn(|_| B::default()); + if let Some(model) = dot_vox_data.models.get(0) { + for (i, col) in dot_vox_data + .palette + .iter() + .map(|col| Rgb::new(col.r, col.g, col.b)) + .enumerate() + { + palette[(i + 1).min(255)] = to_block(col); + } + + let mut vol = Dyna::filled( + Vec3::new(model.size.x, model.size.y, model.size.z), + None, + (), + ); + + for voxel in &model.voxels { + let _ = vol.set( + Vec3::new(voxel.x, voxel.y, voxel.z).map(i32::from), + Some(NonZeroU8::new(voxel.i + 1).unwrap()), + ); + } + + BaseStructure { vol, palette } + } else { + BaseStructure { + vol: Dyna::filled(Vec3::zero(), None, ()), + palette, + } + } +} + +impl assets::Compound for BaseStructure { fn load(cache: assets::AnyCache, specifier: &assets::SharedString) -> Result { let dot_vox_data = cache.load::(specifier)?.read(); let dot_vox_data = &dot_vox_data.0; - if let Some(model) = dot_vox_data.models.get(0) { - let mut palette = std::array::from_fn(|_| StructureBlock::None); - - for (i, col) in dot_vox_data - .palette - .iter() - .map(|col| Rgb::new(col.r, col.g, col.b)) - .enumerate() - { - palette[(i + 1).min(255)] = StructureBlock::Filled(BlockKind::Misc, col); - } - - let mut vol = Dyna::filled( - Vec3::new(model.size.x, model.size.y, model.size.z), - None, - (), - ); - - for voxel in &model.voxels { - let _ = vol.set( - Vec3::new(voxel.x, voxel.y, voxel.z).map(i32::from), - Some(NonZeroU8::new(voxel.i + 1).unwrap()), - ); - } - - Ok(BaseStructure { vol, palette }) - } else { - Ok(BaseStructure { - vol: Dyna::filled(Vec3::zero(), None, ()), - palette: std::array::from_fn(|_| StructureBlock::None), - }) - } + Ok(load_base_structure(dot_vox_data, |col| StructureBlock::Filled(BlockKind::Misc, col))) } } diff --git a/common/src/vol.rs b/common/src/vol.rs index 0025f5f54f..7ed54cd2a0 100644 --- a/common/src/vol.rs +++ b/common/src/vol.rs @@ -241,6 +241,7 @@ where /// Convenience iterator type that can be used to quickly implement /// `IntoPosIterator`. +#[derive(Clone)] pub struct DefaultPosIterator { current: Vec3, begin: Vec2, @@ -288,6 +289,7 @@ impl Iterator for DefaultPosIterator { /// Convenience iterator type that can be used to quickly implement /// `IntoVolIterator`. +#[derive(Clone)] pub struct DefaultVolIterator<'a, T: ReadVol> { vol: &'a T, pos_iter: DefaultPosIterator, diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index 49d59a630f..c200addc0a 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -1,6 +1,7 @@ use crate::{ mesh::{ greedy::{self, GreedyConfig, GreedyMesh}, + terrain::FaceKind, MeshGen, }, render::{Mesh, ParticleVertex, SpriteVertex, TerrainVertex}, @@ -8,6 +9,7 @@ use crate::{ }; use common::{ figure::Cell, + terrain::Block, vol::{BaseVol, ReadVol, SizedVol, Vox}, }; use core::convert::TryFrom; @@ -16,7 +18,7 @@ use vek::*; // /// NOTE: bone_idx must be in [0, 15] (may be bumped to [0, 31] at some // /// point). // TODO: this function name... -pub fn generate_mesh_base_vol_terrain<'a: 'b, 'b, V: 'a>( +pub fn generate_mesh_base_vol_figure<'a: 'b, 'b, V: 'a>( vol: V, (greedy, opaque_mesh, offs, scale, bone_idx): ( &'b mut GreedyMesh<'a>, @@ -115,6 +117,120 @@ where (Mesh::new(), Mesh::new(), Mesh::new(), bounds) } +pub fn generate_mesh_base_vol_terrain<'a: 'b, 'b, V: 'a>( + vol: V, + (greedy, opaque_mesh, offs, scale, bone_idx): ( + &'b mut GreedyMesh<'a>, + &'b mut Mesh, + Vec3, + Vec3, + u8, + ), +) -> MeshGen> +where + V: BaseVol + ReadVol + SizedVol, +{ + assert!(bone_idx <= 15, "Bone index for figures must be in [0, 15]"); + let max_size = greedy.max_size(); + // NOTE: Required because we steal two bits from the normal in the shadow uint + // in order to store the bone index. The two bits are instead taken out + // of the atlas coordinates, which is why we "only" allow 1 << 15 per + // coordinate instead of 1 << 16. + assert!(max_size.reduce_max() < 1 << 15); + + let lower_bound = vol.lower_bound(); + let upper_bound = vol.upper_bound(); + assert!( + lower_bound.x <= upper_bound.x + && lower_bound.y <= upper_bound.y + && lower_bound.z <= upper_bound.z + ); + // NOTE: Figure sizes should be no more than 512 along each axis. + let greedy_size = upper_bound - lower_bound + 1; + assert!(greedy_size.x <= 512 && greedy_size.y <= 512 && greedy_size.z <= 512); + // NOTE: Cast to usize is safe because of previous check, since all values fit + // into u16 which is safe to cast to usize. + let greedy_size = greedy_size.as_::(); + let greedy_size_cross = greedy_size; + let draw_delta = lower_bound; + + let get_light = |vol: &mut V, pos: Vec3| { + if vol.get(pos).map_or(true, |vox| vox.is_fluid()) { + 1.0 + } else { + 0.0 + } + }; + let get_ao = |vol: &mut V, pos: Vec3| { + if vol.get(pos).map_or(false, |vox| vox.is_opaque()) { + 0.0 + } else { + 1.0 + } + }; + let get_glow = |vol: &mut V, pos: Vec3| { + vol.get(pos) + .ok() + .and_then(|vox| vox.get_glow()) + .unwrap_or(0) as f32 + / 255.0 + }; + let get_opacity = |vol: &mut V, pos: Vec3| vol.get(pos).map_or(true, |vox| vox.is_fluid()); + let should_draw = |vol: &mut V, pos: Vec3, delta: Vec3, _uv| { + super::terrain::should_draw_greedy(pos, delta, &|vox| { + vol.get(vox) + .map(|vox| *vox) + .unwrap_or_else(|_| Block::empty()) + }) + }; + + let create_opaque = |atlas_pos, pos, norm| { + TerrainVertex::new_figure(atlas_pos, (pos + offs) * scale, norm, bone_idx) + }; + + greedy.push(GreedyConfig { + data: vol, + draw_delta, + greedy_size, + greedy_size_cross, + get_ao, + get_light, + get_glow, + get_opacity, + should_draw, + push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &FaceKind| match meta { + FaceKind::Opaque(meta) => { + opaque_mesh.push_quad(greedy::create_quad( + atlas_origin, + dim, + origin, + draw_dim, + norm, + meta, + |atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm), + )); + }, + FaceKind::Fluid => {}, + }, + make_face_texel: |vol: &mut V, pos, light, _, _| { + let block = vol.get(pos).ok(); + let glowy = block.map(|c| c.get_glow().is_some()).unwrap_or_default(); + let col = block + .and_then(|vox| vox.get_color()) + .unwrap_or_else(Rgb::zero); + TerrainVertex::make_col_light_figure(light, glowy, false, col) + }, + }); + let bounds = math::Aabb { + // NOTE: Casts are safe since lower_bound and upper_bound both fit in a i16. + min: math::Vec3::from((lower_bound.as_::() + offs) * scale), + max: math::Vec3::from((upper_bound.as_::() + offs) * scale), + } + .made_valid(); + + (Mesh::new(), Mesh::new(), Mesh::new(), bounds) +} + pub fn generate_mesh_base_vol_sprite<'a: 'b, 'b, V: 'a>( vol: V, (greedy, opaque_mesh, vertical_stripes): ( diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index f9a0a8767e..ef3b6d0028 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -20,7 +20,7 @@ use tracing::error; use vek::*; #[derive(Clone, Copy, PartialEq)] -enum FaceKind { +pub enum FaceKind { /// Opaque face that is facing something non-opaque; either /// water (Opaque(true)) or something else (Opaque(false)). Opaque(bool), @@ -537,7 +537,7 @@ pub fn generate_mesh<'a>( /// NOTE: Make sure to reflect any changes to how meshing is performanced in /// [scene::terrain::Terrain::skip_remesh]. -fn should_draw_greedy( +pub fn should_draw_greedy( pos: Vec3, delta: Vec3, flat_get: impl Fn(Vec3) -> Block, diff --git a/voxygen/src/render/pipelines/terrain.rs b/voxygen/src/render/pipelines/terrain.rs index 8602f2804f..fdcfe24475 100644 --- a/voxygen/src/render/pipelines/terrain.rs +++ b/voxygen/src/render/pipelines/terrain.rs @@ -137,17 +137,28 @@ impl VertexTrait for Vertex { #[derive(Copy, Clone, Debug, Zeroable, Pod)] // TODO: new function and private fields?? pub struct Locals { - model_offs: [f32; 3], - load_time: f32, + model_mat0: [f32; 4], + model_mat1: [f32; 4], + model_mat2: [f32; 4], + model_mat3: [f32; 4], atlas_offs: [i32; 4], + load_time: f32, + _dummy: [f32; 3], } impl Locals { - pub fn new(model_offs: Vec3, atlas_offs: Vec2, load_time: f32) -> Self { + pub fn new(model_offs: Vec3, ori: Quaternion, atlas_offs: Vec2, load_time: f32) -> Self { + let mat = Mat4::from(ori).translated_3d(model_offs); + + let mat_arr = mat.into_col_arrays(); Self { - model_offs: model_offs.into_array(), + model_mat0: mat_arr[0], + model_mat1: mat_arr[1], + model_mat2: mat_arr[2], + model_mat3: mat_arr[3], load_time, atlas_offs: Vec4::new(atlas_offs.x as i32, atlas_offs.y as i32, 0, 0).into_array(), + _dummy: [0.0; 3], } } } @@ -155,9 +166,13 @@ impl Locals { impl Default for Locals { fn default() -> Self { Self { - model_offs: [0.0; 3], + model_mat0: [1.0, 0.0, 0.0, 0.0], + model_mat1: [0.0, 1.0, 0.0, 0.0], + model_mat2: [0.0, 0.0, 1.0, 0.0], + model_mat3: [0.0, 0.0, 0.0, 1.0], load_time: 0.0, atlas_offs: [0; 4], + _dummy: [0.0; 3], } } } diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index cdc80e140b..110c29f419 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -1266,13 +1266,10 @@ impl Renderer { } /// Create a new set of instances with the provided values. - pub fn create_instances( - &mut self, - vals: &[T], - ) -> Result, RenderError> { + pub fn create_instances(&mut self, vals: &[T]) -> Instances { let mut instances = Instances::new(&self.device, vals.len()); instances.update(&self.queue, vals, 0); - Ok(instances) + instances } /// Ensure that the quad index buffer is large enough for a quad vertex diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs index 181e52756c..2a1dc4022f 100644 --- a/voxygen/src/render/renderer/drawer.rs +++ b/voxygen/src/render/renderer/drawer.rs @@ -1,3 +1,5 @@ +use crate::render::Bound; + use super::{ super::{ buffer::Buffer, @@ -1113,9 +1115,9 @@ pub struct SpriteDrawer<'pass_ref, 'pass: 'pass_ref> { } impl<'pass_ref, 'pass: 'pass_ref> SpriteDrawer<'pass_ref, 'pass> { - pub fn draw<'data: 'pass>( + pub fn draw<'data: 'pass, T>( &mut self, - terrain_locals: &'data terrain::BoundLocals, + terrain_locals: &'data Bound, instances: &'data Instances, alt_indices: &'data AltIndices, culling_mode: CullingMode, diff --git a/voxygen/src/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs index 4dc7486065..a0d6d622dc 100644 --- a/voxygen/src/scene/figure/cache.rs +++ b/voxygen/src/scene/figure/cache.rs @@ -1,8 +1,21 @@ -use super::{load::BodySpec, FigureModelEntry}; +use super::{ + load::{BodySpec, ShipBoneMeshes}, + FigureModelEntry, ModelEntry, TerrainModelEntry, +}; use crate::{ - mesh::{greedy::GreedyMesh, segment::generate_mesh_base_vol_terrain}, - render::{BoneMeshes, ColLightInfo, FigureModel, Mesh, Renderer, TerrainVertex}, - scene::camera::CameraMode, + mesh::{ + greedy::GreedyMesh, + segment::{generate_mesh_base_vol_figure, generate_mesh_base_vol_terrain}, + }, + render::{ + pipelines::terrain::{BoundLocals as BoundTerrainLocals, Locals as TerrainLocals}, BoneMeshes, ColLightInfo, + FigureModel, Instances, Mesh, Renderer, SpriteInstance, TerrainVertex, + }, + scene::{ + camera::CameraMode, + terrain::{get_sprite_instances, BlocksOfInterest, SPRITE_LOD_LEVELS}, + Terrain, + }, }; use anim::Skeleton; use common::{ @@ -15,36 +28,57 @@ use common::{ item::{item_key::ItemKey, modular, Item, ItemDefinitionId}, CharacterState, }, - figure::Segment, + figure::{Segment, TerrainSegment}, slowjob::SlowJobPool, - vol::BaseVol, + vol::{BaseVol, IntoVolIterator}, }; use core::{hash::Hash, ops::Range}; use crossbeam_utils::atomic; use hashbrown::{hash_map::Entry, HashMap}; use serde::Deserialize; -use std::sync::Arc; +use std::{sync::Arc, array::from_fn}; use vek::*; /// A type produced by mesh worker threads corresponding to the information /// needed to mesh figures. -struct MeshWorkerResponse { +pub struct MeshWorkerResponse { col_light: ColLightInfo, opaque: Mesh, bounds: anim::vek::Aabb, vertex_range: [Range; N], } +/// A type produced by mesh worker threads corresponding to the information +/// needed to mesh figures. +pub struct TerrainMeshWorkerResponse { + col_light: ColLightInfo, + opaque: Mesh, + bounds: anim::vek::Aabb, + vertex_range: [Range; N], + sprite_instances: [Vec; SPRITE_LOD_LEVELS], + blocks_of_interest: BlocksOfInterest, +} + /// NOTE: To test this cell for validity, we currently first use /// Arc::get_mut(), and then only if that succeeds do we call AtomicCell::take. /// This way, we avoid all atomic updates for the fast path read in the "not yet /// updated" case (though it would be faster without weak pointers); since once /// it's updated, we switch from `Pending` to `Done`, this is only suboptimal /// for one frame. -type MeshWorkerCell = atomic::AtomicCell>>; +pub type MeshWorkerCell = atomic::AtomicCell>>; +pub type TerrainMeshWorkerCell = + atomic::AtomicCell>>; + +pub trait ModelEntryFuture { + type ModelEntry: ModelEntry; + + fn into_done(self) -> Option; + + fn get_done(&self) -> Option<&Self::ModelEntry>; +} /// A future FigureModelEntryLod. -enum FigureModelEntryFuture { +pub enum FigureModelEntryFuture { /// We can poll the future to see whether the figure model is ready. // TODO: See if we can find away to either get rid of this Arc, or reuse Arcs across different // figures. Updates to uvth for thread pool shared storage might obviate this requirement. @@ -53,9 +87,56 @@ enum FigureModelEntryFuture { Done(FigureModelEntry), } +impl ModelEntryFuture for FigureModelEntryFuture { + type ModelEntry = FigureModelEntry; + + fn into_done(self) -> Option { + match self { + Self::Pending(_) => None, + Self::Done(d) => Some(d), + } + } + + fn get_done(&self) -> Option<&Self::ModelEntry> { + match self { + Self::Pending(_) => None, + Self::Done(d) => Some(d), + } + } +} + +/// A future TerrainModelEntryLod. +pub enum TerrainModelEntryFuture { + /// We can poll the future to see whether the figure model is ready. + // TODO: See if we can find away to either get rid of this Arc, or reuse Arcs across different + // figures. Updates to uvth for thread pool shared storage might obviate this requirement. + Pending(Arc>), + /// Stores the already-meshed model. + Done(TerrainModelEntry), +} + +impl ModelEntryFuture for TerrainModelEntryFuture { + type ModelEntry = TerrainModelEntry; + + fn into_done(self) -> Option { + match self { + Self::Pending(_) => None, + Self::Done(d) => Some(d), + } + } + + fn get_done(&self) -> Option<&Self::ModelEntry> { + match self { + Self::Pending(_) => None, + Self::Done(d) => Some(d), + } + } +} + const LOD_COUNT: usize = 3; type FigureModelEntryLod<'b> = Option<&'b FigureModelEntry>; +type TerrainModelEntryLod<'b> = Option<&'b TerrainModelEntry>; #[derive(Clone, Eq, Hash, PartialEq)] /// TODO: merge item_key and extra field into an enum @@ -199,7 +280,16 @@ where Skel: Skeleton, Skel::Body: BodySpec, { - models: HashMap, ((FigureModelEntryFuture, Skel::Attr), u64)>, + models: HashMap< + FigureKey, + ( + ( + ::ModelEntryFuture, + Skel::Attr, + ), + u64, + ), + >, manifests: ::Manifests, watcher: ReloadWatcher, } @@ -231,16 +321,16 @@ where /// the model, we don't return skeleton data. pub fn get_model<'b>( &'b self, - // TODO: If we ever convert to using an atlas here, use this. + // TODO: If we ever convert to using an atlas here, use this. _col_lights: &super::FigureColLights, body: Skel::Body, inventory: Option<&Inventory>, - // TODO: Consider updating the tick by putting it in a Cell. + // TODO: Consider updating the tick by putting it in a Cell. _tick: u64, camera_mode: CameraMode, character_state: Option<&CharacterState>, item_key: Option, - ) -> FigureModelEntryLod<'b> { + ) -> Option<&<::ModelEntryFuture as ModelEntryFuture>::ModelEntry>{ // TODO: Use raw entries to avoid lots of allocation (among other things). let key = FigureKey { body, @@ -254,13 +344,44 @@ where }), }; - if let Some(((FigureModelEntryFuture::Done(model), _), _)) = self.models.get(&key) { + if let Some(model) = self.models.get(&key).and_then(|d| d.0.0.get_done()) { Some(model) } else { None } } + pub fn clear_models(&mut self) { self.models.clear(); } + + pub fn clean(&mut self, col_lights: &mut super::FigureColLights, tick: u64) + where + ::Spec: Clone, + { + // TODO: Don't hard-code this. + if tick % 60 == 0 { + self.models.retain(|_, ((model_entry, _), last_used)| { + // Wait about a minute at 60 fps before invalidating old models. + let delta = 60 * 60; + let alive = *last_used + delta > tick; + if !alive { + if let Some(model_entry) = model_entry.get_done() { + col_lights.atlas.deallocate(model_entry.allocation().id); + } + } + alive + }); + } + } +} + +impl FigureModelCache +where + Skel::Body: BodySpec< + BoneMesh = super::load::BoneMeshes, + ModelEntryFuture = FigureModelEntryFuture, + > + Eq + + Hash, +{ #[allow(clippy::too_many_arguments)] pub fn get_or_create_model<'c>( &'c mut self, @@ -408,7 +529,7 @@ where offset: Vec3, bone_idx: u8, ) -> BoneMeshes { - let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain( + let (opaque, _, _, bounds) = generate_mesh_base_vol_figure( segment, (greedy, opaque_mesh, offset, Vec3::one(), bone_idx), ); @@ -423,7 +544,7 @@ where bone_idx: u8, ) -> BoneMeshes { let lod_scale = 0.6; - let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain( + let (opaque, _, _, bounds) = generate_mesh_base_vol_figure( segment.scaled_by(Vec3::broadcast(lod_scale)), ( greedy, @@ -444,7 +565,7 @@ where bone_idx: u8, ) -> BoneMeshes { let lod_scale = 0.3; - let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain( + let (opaque, _, _, bounds) = generate_mesh_base_vol_figure( segment.scaled_by(Vec3::broadcast(lod_scale)), ( greedy, @@ -479,26 +600,316 @@ where }, } } +} - pub fn clear_models(&mut self) { self.models.clear(); } - - pub fn clean(&mut self, col_lights: &mut super::FigureColLights, tick: u64) +impl FigureModelCache +where + Skel::Body: BodySpec< + BoneMesh = ShipBoneMeshes, + ModelEntryFuture = TerrainModelEntryFuture, + > + Eq + + Hash, +{ + #[allow(clippy::too_many_arguments)] + pub fn get_or_create_terrain_model<'c>( + &'c mut self, + renderer: &mut Renderer, + col_lights: &mut super::FigureColLights, + pos: Vec3, + ori: Quaternion, + body: Skel::Body, + extra: ::Extra, + tick: u64, + slow_jobs: &SlowJobPool, + terrain: &Terrain, + ) -> (TerrainModelEntryLod<'c>, &'c Skel::Attr) where - ::Spec: Clone, + for<'a> &'a Skel::Body: Into, + Skel::Body: Clone + Send + Sync + 'static, + ::Spec: Send + Sync + 'static, { - // TODO: Don't hard-code this. - if tick % 60 == 0 { - self.models.retain(|_, ((model_entry, _), last_used)| { - // Wait about a minute at 60 fps before invalidating old models. - let delta = 60 * 60; - let alive = *last_used + delta > tick; - if !alive { - if let FigureModelEntryFuture::Done(model_entry) = model_entry { - col_lights.atlas.deallocate(model_entry.allocation.id); + let skeleton_attr = (&body).into(); + let key = FigureKey { + body, + item_key: None, + extra: None, + }; + + // TODO: Use raw entries to avoid significant performance overhead. + match self.models.entry(key) { + Entry::Occupied(o) => { + let ((model, skel), last_used) = o.into_mut(); + *last_used = tick; + ( + match model { + TerrainModelEntryFuture::Pending(recv) => { + if let Some(TerrainMeshWorkerResponse { + col_light, + opaque, + bounds, + vertex_range, + sprite_instances, + blocks_of_interest, + }) = Arc::get_mut(recv).take().and_then(|cell| cell.take()) + { + let model_entry = col_lights.create_terrain( + renderer, + col_light, + (opaque, bounds), + vertex_range, + pos, + ori, + sprite_instances, + blocks_of_interest, + ); + *model = TerrainModelEntryFuture::Done(model_entry); + // NOTE: Borrow checker isn't smart enough to figure this out. + if let TerrainModelEntryFuture::Done(model) = model { + Some(model) + } else { + unreachable!(); + } + } else { + None + } + }, + TerrainModelEntryFuture::Done(model) => Some(model), + }, + skel, + ) + }, + Entry::Vacant(v) => { + let key = v.key().clone(); + let slot = Arc::new(atomic::AtomicCell::new(None)); + let manifests = self.manifests.clone(); + let sprite_data = Arc::clone(&terrain.sprite_data); + let sprite_config = Arc::clone(&terrain.sprite_config); + let slot_ = Arc::clone(&slot); + + slow_jobs.spawn("FIGURE_MESHING", move || { + // First, load all the base vertex data. + let meshes = + ::bone_meshes(&key, &manifests, extra); + + // Then, set up meshing context. + let mut greedy = FigureModel::make_greedy(); + let mut opaque = Mesh::::new(); + // Choose the most conservative bounds for any LOD model. + let mut figure_bounds = anim::vek::Aabb { + min: anim::vek::Vec3::zero(), + max: anim::vek::Vec3::zero(), + }; + // Meshes all bone models for this figure using the given mesh generation + // function, attaching it to the current greedy mesher and opaque vertex + // list. Returns the vertex bounds of the meshed model within the opaque + // mesh. + let mut make_model = |generate_mesh: for<'a, 'b> fn( + &mut GreedyMesh<'a>, + &'b mut _, + &'a _, + _, + _, + ) + -> _| { + let vertex_start = opaque.vertices().len(); + meshes + .iter() + .enumerate() + // NOTE: Cast to u8 is safe because i < 16. + .filter_map(|(i, bm)| bm.as_ref().map(|bm| (i as u8, bm))) + .for_each(|(i, (segment, offset))| { + // Generate this mesh. + let (_opaque_mesh, bounds) = generate_mesh(&mut greedy, &mut opaque, segment, *offset, i); + // Update the figure bounds to the largest granularity seen so far + // (NOTE: this is more than a little imperfect). + // + // FIXME: Maybe use the default bone position in the idle animation + // to figure this out instead? + figure_bounds.expand_to_contain(bounds); + }); + // NOTE: vertex_start and vertex_end *should* fit in a u32, by the + // following logic: + // + // Our new figure maximum is constrained to at most 2^8 × 2^8 × 2^8. + // This uses at most 24 bits to store every vertex exactly once. + // Greedy meshing can store each vertex in up to 3 quads, we have 3 + // greedy models, and we store 1.5x the vertex count, so the maximum + // total space a model can take up is 3 * 3 * 1.5 * 2^24; rounding + // up to 4 * 4 * 2^24 gets us to 2^28, which clearly still fits in a + // u32. + // + // (We could also, though we prefer not to, reason backwards from the + // maximum figure texture size of 2^15 × 2^15, also fits in a u32; we + // can also see that, since we can have at most one texture entry per + // vertex, any texture atlas of size 2^14 × 2^14 or higher should be + // able to store data for any figure. So the only reason we would fail + // here would be if the user's computer could not store a texture large + // enough to fit all the LOD models for the figure, not for fundamental + // reasons related to fitting in a u32). + // + // Therefore, these casts are safe. + vertex_start as u32..opaque.vertices().len() as u32 + }; + + fn generate_mesh<'a>( + greedy: &mut GreedyMesh<'a>, + opaque_mesh: &mut Mesh, + segment: &'a TerrainSegment, + offset: Vec3, + bone_idx: u8, + ) -> BoneMeshes { + let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain( + segment, + (greedy, opaque_mesh, offset, Vec3::one(), bone_idx), + ); + (opaque, bounds) } - } - alive - }); + + fn generate_mesh_lod_mid<'a>( + greedy: &mut GreedyMesh<'a>, + opaque_mesh: &mut Mesh, + segment: &'a TerrainSegment, + offset: Vec3, + bone_idx: u8, + ) -> BoneMeshes { + let lod_scale = 0.6; + let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain( + segment.scaled_by(Vec3::broadcast(lod_scale)), + ( + greedy, + opaque_mesh, + offset * lod_scale, + Vec3::one() / lod_scale, + bone_idx, + ), + ); + (opaque, bounds) + } + + fn generate_mesh_lod_low<'a>( + greedy: &mut GreedyMesh<'a>, + opaque_mesh: &mut Mesh, + segment: &'a TerrainSegment, + offset: Vec3, + bone_idx: u8, + ) -> BoneMeshes { + let lod_scale = 0.3; + let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain( + segment.scaled_by(Vec3::broadcast(lod_scale)), + ( + greedy, + opaque_mesh, + offset * lod_scale, + Vec3::one() / lod_scale, + bone_idx, + ), + ); + (opaque, bounds) + } + + let models = [ + make_model(generate_mesh), + make_model(generate_mesh_lod_mid), + make_model(generate_mesh_lod_low), + ]; + + let (dyna, offset) = &meshes[0].as_ref().unwrap(); + let block_iter = dyna.vol_iter(Vec3::zero(), dyna.sz.as_()).map(|(pos, block)| (pos, *block)); + + slot_.store(Some(TerrainMeshWorkerResponse { + col_light: greedy.finalize(), + opaque, + bounds: figure_bounds, + vertex_range: models, + sprite_instances: { + let mut instances = from_fn(|_| Vec::new()); + get_sprite_instances( + &mut instances, + |lod, instance, _| { + lod.push(instance); + }, + block_iter.clone().map(|(pos, block)| (pos.as_() + *offset, block)), + |p| p.as_(), |_| 1.0, |_| 0.0, + &sprite_data, + &sprite_config, + ); + instances + }, + blocks_of_interest: BlocksOfInterest::from_blocks(block_iter, 0.0, 10.0, 0.0), + })); + }); + + let skel = &(v + .insert(( + (TerrainModelEntryFuture::Pending(slot), skeleton_attr), + tick, + )) + .0) + .1; + (None, skel) + }, + } + } + + pub fn get_blocks_of_interest<'c>( + &'c self, + body: Skel::Body, + ) -> Option<&'c BlocksOfInterest> { + let key = FigureKey { + body, + item_key: None, + extra: None, + }; + self.models.get(&key).and_then(|((model, _), _)| { + let TerrainModelEntryFuture::Done(model) = model else { + return None; + }; + + Some(&model.blocks_of_interest) + }) + } + + pub fn get_sprites<'c>( + &'c self, + body: Skel::Body, + ) -> Option<( + &'c BoundTerrainLocals, + &'c [Instances; SPRITE_LOD_LEVELS], + )> { + let key = FigureKey { + body, + item_key: None, + extra: None, + }; + self.models.get(&key).and_then(|((model, _), _)| { + let TerrainModelEntryFuture::Done(model) = model else { + return None; + }; + + Some((&model.terrain_locals, &model.sprite_instances)) + }) + } + + pub fn update_terrain_locals( + &mut self, + renderer: &mut Renderer, + body: Skel::Body, + pos: Vec3, + ori: Quaternion, + ) { + let key = FigureKey { + body, + item_key: None, + extra: None, + }; + if let Some(model) = self.models.get_mut(&key).and_then(|((model, _), _)| { + if let TerrainModelEntryFuture::Done(model) = model { + Some(model) + } else { + None + } + }) { + renderer.update_consts(&mut *model.terrain_locals, &[TerrainLocals::new(pos, ori, Vec2::zero(), 0.0)]) } } } diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index 3a9cd6a6c0..604e8ece47 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -1,4 +1,4 @@ -use super::cache::{FigureKey, ToolKey}; +use super::cache::{FigureKey, ToolKey, TerrainModelEntryFuture, FigureModelEntryFuture, ModelEntryFuture}; use common::{ assets::{self, AssetExt, AssetHandle, DotVoxAsset, ReloadWatcher, Ron}, comp::{ @@ -19,12 +19,12 @@ use common::{ quadruped_small::{self, BodyType as QSBodyType, Species as QSSpecies}, ship::{ self, - figuredata::{ShipCentralSubSpec, ShipSpec}, + figuredata::{ShipSpec, VoxelCollider}, }, theropod::{self, BodyType as TBodyType, Species as TSpecies}, }, figure::{Cell, DynaUnionizer, MatCell, MatSegment, Material, Segment}, - vol::{IntoFullPosIterator, ReadVol, Vox}, + vol::{IntoFullPosIterator, ReadVol, Vox}, terrain::Block, volumes::dyna::Dyna, }; use hashbrown::HashMap; use serde::{Deserialize, Deserializer}; @@ -108,6 +108,8 @@ pub trait BodySpec: Sized { /// place it behind an [`Arc`]. type Manifests: Send + Sync + Clone; type Extra: Send + Sync; + type BoneMesh; + type ModelEntryFuture: ModelEntryFuture; /// Initialize all the specifications for this Body. fn load_spec() -> Result; @@ -126,7 +128,7 @@ pub trait BodySpec: Sized { key: &FigureKey, manifests: &Self::Manifests, extra: Self::Extra, - ) -> [Option; anim::MAX_BONE_COUNT]; + ) -> [Option; anim::MAX_BONE_COUNT]; } macro_rules! make_vox_spec { @@ -152,6 +154,8 @@ macro_rules! make_vox_spec { type Spec = $Spec; type Manifests = AssetHandle; type Extra = (); + type BoneMesh = BoneMeshes; + type ModelEntryFuture = FigureModelEntryFuture; fn load_spec() -> Result { Self::Spec::load("") @@ -5258,31 +5262,32 @@ fn segment_center(segment: &Segment) -> Option> { } } -fn mesh_ship_bone &ShipCentralSubSpec>( +pub type ShipBoneMeshes = (Dyna, Vec3); + +fn mesh_ship_bone<'a, K: fmt::Debug + Eq + Hash, V, F: Fn(&V) -> Option<&'a VoxelCollider>>( map: &HashMap, obj: &K, f: F, -) -> BoneMeshes { +) -> Option { let spec = match map.get(obj) { Some(spec) => spec, None => { error!("No specification exists for {:?}", obj); - return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + + return None; }, }; let bone = f(spec); - let central = graceful_load_segment_fullspec( - &format!("common.voxel.{}", &bone.central.0), - bone.model_index, - ); - (central, Vec3::from(bone.offset)) + bone.map(|bone| (bone.volume().clone(), bone.translation)) } impl BodySpec for ship::Body { type Extra = (); type Manifests = AssetHandle; type Spec = ShipSpec; + type BoneMesh = ShipBoneMeshes; + type ModelEntryFuture = TerrainModelEntryFuture; fn load_spec() -> Result { Self::Spec::load("") } @@ -5292,14 +5297,15 @@ impl BodySpec for ship::Body { FigureKey { body, .. }: &FigureKey, manifests: &Self::Manifests, _: Self::Extra, - ) -> [Option; anim::MAX_BONE_COUNT] { - let spec = &*manifests.read(); - let map = &(spec.central.read().0).0; + ) -> [Option; anim::MAX_BONE_COUNT] { + let spec = manifests.read(); + let spec = &*spec; + let map = &spec.central.read().0.0; [ - Some(mesh_ship_bone(map, body, |spec| &spec.bone0)), - Some(mesh_ship_bone(map, body, |spec| &spec.bone1)), - Some(mesh_ship_bone(map, body, |spec| &spec.bone2)), - Some(mesh_ship_bone(map, body, |spec| &spec.bone3)), + mesh_ship_bone(map, body, |ship| spec.colliders.get(&ship.bone0.central.0)), + mesh_ship_bone(map, body, |ship| spec.colliders.get(&ship.bone1.central.0)), + mesh_ship_bone(map, body, |ship| spec.colliders.get(&ship.bone2.central.0)), + mesh_ship_bone(map, body, |ship| spec.colliders.get(&ship.bone3.central.0)), None, None, None, diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 1e84172d2f..f10d13a656 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -9,9 +9,14 @@ pub use volume::VolumeKey; use crate::{ ecs::comp::Interpolated, render::{ - pipelines::{self, trail, ColLights}, + pipelines::{ + self, + terrain::{BoundLocals as BoundTerrainLocals, Locals as TerrainLocals}, + trail, ColLights, + }, ColLightInfo, FigureBoneData, FigureDrawer, FigureLocals, FigureModel, FigureShadowDrawer, - Mesh, Quad, RenderError, Renderer, SubModel, TerrainVertex, + Instances, Mesh, Quad, RenderError, Renderer, SpriteDrawer, SpriteInstance, SubModel, + TerrainVertex, CullingMode, AltIndices, }, scene::{ camera::{Camera, CameraMode, Dependents}, @@ -61,6 +66,8 @@ use std::sync::Arc; use treeculler::{BVol, BoundingSphere}; use vek::*; +use super::terrain::{BlocksOfInterest, SPRITE_LOD_LEVELS}; + const DAMAGE_FADE_COEFFICIENT: f64 = 15.0; const MOVING_THRESHOLD: f32 = 0.2; const MOVING_THRESHOLD_SQR: f32 = MOVING_THRESHOLD * MOVING_THRESHOLD; @@ -75,6 +82,14 @@ pub type FigureModelRef<'a> = ( &'a ColLights, ); +pub trait ModelEntry { + fn allocation(&self) -> &guillotiere::Allocation; + + fn lod_model(&self, lod: usize) -> Option>; + + fn col_lights(&self) -> &ColLights; +} + /// An entry holding enough information to draw or destroy a figure in a /// particular cache. pub struct FigureModelEntry { @@ -95,14 +110,79 @@ pub struct FigureModelEntry { model: FigureModel, } -impl FigureModelEntry { - pub fn lod_model(&self, lod: usize) -> Option> { +impl ModelEntry for FigureModelEntry { + fn allocation(&self) -> &guillotiere::Allocation { &self.allocation } + + fn lod_model(&self, lod: usize) -> Option> { // Note: Range doesn't impl Copy even for trivially Cloneable things self.model .opaque .as_ref() .map(|m| m.submodel(self.lod_vertex_ranges[lod].clone())) } + + fn col_lights(&self) -> &ColLights { &self.col_lights } +} + +/// An entry holding enough information to draw or destroy a figure in a +/// particular cache. +pub struct TerrainModelEntry { + /// The estimated bounds of this figure, in voxels. This may not be very + /// useful yet. + _bounds: math::Aabb, + /// Hypothetical texture atlas allocation data for the current figure. + /// Will be useful if we decide to use a packed texture atlas for figures + /// like we do for terrain. + allocation: guillotiere::Allocation, + /// Texture used to store color/light information for this figure entry. + /* TODO: Consider using mipmaps instead of storing multiple texture atlases for different + * LOD levels. */ + col_lights: ColLights, + /// Vertex ranges stored in this figure entry; there may be several for one + /// figure, because of LOD models. + lod_vertex_ranges: [Range; N], + model: FigureModel, + + terrain_locals: BoundTerrainLocals, + sprite_instances: [Instances; SPRITE_LOD_LEVELS], + + blocks_of_interest: BlocksOfInterest, +} + +impl ModelEntry for TerrainModelEntry { + fn allocation(&self) -> &guillotiere::Allocation { &self.allocation } + + fn lod_model(&self, lod: usize) -> Option> { + // Note: Range doesn't impl Copy even for trivially Cloneable things + self.model + .opaque + .as_ref() + .map(|m| m.submodel(self.lod_vertex_ranges[lod].clone())) + } + + fn col_lights(&self) -> &ColLights { &self.col_lights } +} + +#[derive(Clone, Copy)] +pub enum ModelEntryRef<'a, const N: usize> { + Figure(&'a FigureModelEntry), + Terrain(&'a TerrainModelEntry), +} + +impl<'a, const N: usize> ModelEntryRef<'a, N> { + fn lod_model(&self, lod: usize) -> Option> { + match self { + ModelEntryRef::Figure(e) => e.lod_model(lod), + ModelEntryRef::Terrain(e) => e.lod_model(lod), + } + } + + fn col_lights(&self) -> &'a ColLights { + match self { + ModelEntryRef::Figure(e) => e.col_lights(), + ModelEntryRef::Terrain(e) => e.col_lights(), + } + } } struct FigureMgrStates { @@ -6002,23 +6082,26 @@ impl FigureMgr { ); }, Body::Ship(body) => { + let Some(terrain) = terrain else { + continue; + }; let (model, skeleton_attr) = if let Some(Collider::Volume(vol)) = collider { let vk = VolumeKey { entity, mut_count: vol.mut_count, }; - let (model, _skeleton_attr) = self.volume_model_cache.get_or_create_model( - renderer, - &mut self.col_lights, - vk, - inventory, - Arc::clone(vol), - tick, - viewpoint_camera_mode, - viewpoint_character_state, - &slow_jobs, - None, - ); + let (model, _skeleton_attr) = + self.volume_model_cache.get_or_create_terrain_model( + renderer, + &mut self.col_lights, + pos.0.into(), + ori.into_vec4().into(), + vk, + Arc::clone(vol), + tick, + &slow_jobs, + terrain, + ); let state = self .states @@ -6036,25 +6119,30 @@ impl FigureMgr { vk, ); - break; + self.volume_model_cache.update_terrain_locals( + renderer, + vk, + pos.0.into(), + ori.into_vec4().into(), + ); + continue; } else if body.manifest_entry().is_some() { - self.ship_model_cache.get_or_create_model( + self.ship_model_cache.get_or_create_terrain_model( renderer, &mut self.col_lights, + pos.0.into(), + ori.into_vec4().into(), body, - inventory, (), tick, - viewpoint_camera_mode, - viewpoint_character_state, &slow_jobs, - None, + terrain, ) } else { // No way to determine model (this is okay, we might just not have received // the `Collider` for the entity yet. Wait until the // next tick. - break; + continue; }; let state = self.states.ship_states.entry(entity).or_insert_with(|| { @@ -6122,6 +6210,13 @@ impl FigureMgr { model, body, ); + + self.ship_model_cache.update_terrain_locals( + renderer, + body, + pos.0.into(), + ori.into_vec4().into(), + ); }, } } @@ -6213,11 +6308,51 @@ impl FigureMgr { }) } + pub fn render_sprites<'a>( + &'a self, + drawer: &mut SpriteDrawer<'_, 'a>, + state: &State, + focus_pos: Vec3, + sprite_render_distance: f32, + ) { + span!(_guard, "render", "FigureManager::render_sprites"); + let ecs = state.ecs(); + let sprite_low_detail_distance = sprite_render_distance * 0.75; + let sprite_mid_detail_distance = sprite_render_distance * 0.5; + let sprite_hid_detail_distance = sprite_render_distance * 0.35; + let sprite_high_detail_distance = sprite_render_distance * 0.15; + + for (entity, pos, body, _, inventory, scale, collider) in ( + &ecs.entities(), + &ecs.read_storage::(), + &ecs.read_storage::(), + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + ) + .join() + // Don't render dead entities + .filter(|(_, _, _, health, _, _, _)| health.map_or(true, |h| !h.is_dead)) + { + if let Some((data, sprite_instances)) = self.get_sprite_instances( + entity, + body, + collider, + ) { + let focus_dist_sqrd = pos.0.distance_squared(focus_pos); + + // TODO NOW: LOD + drawer.draw(data, &sprite_instances[0], &AltIndices { deep_end: 0, underground_end: 0 }, CullingMode::None) + } + } + } + pub fn render<'a>( &'a self, drawer: &mut FigureDrawer<'_, 'a>, state: &State, - player_entity: EcsEntity, + viewpoint_entity: EcsEntity, tick: u64, (camera, figure_lod_render_distance): CameraData, ) { @@ -6225,7 +6360,7 @@ impl FigureMgr { let ecs = state.ecs(); let character_state_storage = state.read_storage::(); - let character_state = character_state_storage.get(player_entity); + let character_state = character_state_storage.get(viewpoint_entity); let items = ecs.read_storage::(); for (entity, pos, body, _, inventory, scale, collider) in ( &ecs.entities(), @@ -6240,7 +6375,7 @@ impl FigureMgr { // Don't render dead entities .filter(|(_, _, _, health, _, _, _)| health.map_or(true, |h| !h.is_dead)) // Don't render player - .filter(|(entity, _, _, _, _, _, _)| *entity != player_entity) + .filter(|(entity, _, _, _, _, _, _)| *entity != viewpoint_entity) { if let Some((bound, model, col_lights)) = self.get_model_for_render( tick, @@ -6273,7 +6408,7 @@ impl FigureMgr { &'a self, drawer: &mut FigureDrawer<'_, 'a>, state: &State, - player_entity: EcsEntity, + viewpoint_entity: EcsEntity, tick: u64, (camera, figure_lod_render_distance): CameraData, ) { @@ -6281,28 +6416,28 @@ impl FigureMgr { let ecs = state.ecs(); let character_state_storage = state.read_storage::(); - let character_state = character_state_storage.get(player_entity); + let character_state = character_state_storage.get(viewpoint_entity); let items = ecs.read_storage::(); if let (Some(pos), Some(body), scale) = ( - ecs.read_storage::().get(player_entity), - ecs.read_storage::().get(player_entity), - ecs.read_storage::().get(player_entity), + ecs.read_storage::().get(viewpoint_entity), + ecs.read_storage::().get(viewpoint_entity), + ecs.read_storage::().get(viewpoint_entity), ) { let healths = state.read_storage::(); - let health = healths.get(player_entity); + let health = healths.get(viewpoint_entity); if health.map_or(false, |h| h.is_dead) { return; } let inventory_storage = ecs.read_storage::(); - let inventory = inventory_storage.get(player_entity); + let inventory = inventory_storage.get(viewpoint_entity); if let Some((bound, model, col_lights)) = self.get_model_for_render( tick, camera, character_state, - player_entity, + viewpoint_entity, body, scale.copied(), inventory, @@ -6312,7 +6447,7 @@ impl FigureMgr { 0, |state| state.visible(), if matches!(body, Body::ItemDrop(_)) { - items.get(player_entity).map(ItemKey::from) + items.get(viewpoint_entity).map(ItemKey::from) } else { None }, @@ -6408,15 +6543,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::QuadrupedSmall(body) => quadruped_small_states @@ -6425,15 +6562,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - quadruped_small_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + quadruped_small_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::QuadrupedMedium(body) => quadruped_medium_states @@ -6442,15 +6581,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - quadruped_medium_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + quadruped_medium_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::QuadrupedLow(body) => quadruped_low_states @@ -6459,15 +6600,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - quadruped_low_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + quadruped_low_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::BirdMedium(body) => bird_medium_states @@ -6476,15 +6619,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - bird_medium_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + bird_medium_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::FishMedium(body) => fish_medium_states @@ -6493,15 +6638,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - fish_medium_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + fish_medium_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::Theropod(body) => theropod_states @@ -6510,15 +6657,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - theropod_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + theropod_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::Dragon(body) => dragon_states @@ -6527,15 +6676,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - dragon_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + dragon_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::BirdLarge(body) => bird_large_states @@ -6544,15 +6695,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - bird_large_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + bird_large_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::FishSmall(body) => fish_small_states @@ -6561,15 +6714,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - fish_small_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + fish_small_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::BipedLarge(body) => biped_large_states @@ -6578,15 +6733,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - biped_large_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + biped_large_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::BipedSmall(body) => biped_small_states @@ -6595,15 +6752,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - biped_small_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + biped_small_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::Golem(body) => golem_states @@ -6612,15 +6771,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - golem_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + golem_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::Arthropod(body) => arthropod_states @@ -6629,15 +6790,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - arthropod_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + arthropod_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::Object(body) => object_states @@ -6646,15 +6809,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - object_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + object_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), ) }), Body::ItemDrop(body) => item_drop_states @@ -6663,15 +6828,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - item_drop_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - item_key, - ), + item_drop_model_cache + .get_model( + col_lights, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + item_key, + ) + .map(ModelEntryRef::Figure), ) }), Body::Ship(body) => { @@ -6682,15 +6849,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - ship_model_cache.get_model( - col_lights, - body, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + ship_model_cache + .get_model( + col_lights, + body, + None, + tick, + CameraMode::default(), + None, + None, + ) + .map(ModelEntryRef::Terrain), ) }) } else { @@ -6700,15 +6869,17 @@ impl FigureMgr { .map(move |state| { ( state.bound(), - volume_model_cache.get_model( - col_lights, - VolumeKey { entity, mut_count }, - inventory, - tick, - viewpoint_camera_mode, - character_state, - None, - ), + volume_model_cache + .get_model( + col_lights, + VolumeKey { entity, mut_count }, + None, + tick, + CameraMode::default(), + None, + None, + ) + .map(ModelEntryRef::Terrain), ) }) } @@ -6748,6 +6919,35 @@ impl FigureMgr { } } + fn get_sprite_instances( + &self, + entity: EcsEntity, + body: &Body, + collider: Option<&Collider>, + ) -> Option<( + &BoundTerrainLocals, + &[Instances; SPRITE_LOD_LEVELS], + )> { + match body { + Body::Ship(body) => { + if let Some(Collider::Volume(vol)) = collider { + let vk = VolumeKey { + entity, + mut_count: vol.mut_count, + }; + self.volume_model_cache.get_sprites( + vk, + ) + } else { + self.ship_model_cache.get_sprites( + *body, + ) + } + }, + _ => None, + } + } + pub fn viewpoint_offset(&self, scene_data: &SceneData, entity: EcsEntity) -> Vec3 { scene_data .state @@ -6866,10 +7066,10 @@ impl FigureColLights { /// Find the correct texture for this model entry. pub fn texture<'a, const N: usize>( &'a self, - model: &'a FigureModelEntry, + model: ModelEntryRef<'a, N>, ) -> &'a ColLights { /* &self.col_lights */ - &model.col_lights + model.col_lights() } /// NOTE: Panics if the opaque model's length does not fit in a u32. @@ -6917,6 +7117,64 @@ impl FigureColLights { } } + /// NOTE: Panics if the opaque model's length does not fit in a u32. + /// This is part of the function contract. + /// + /// NOTE: Panics if the vertex range bounds are not in range of the opaque + /// model stored in the BoneMeshes parameter. This is part of the + /// function contract. + /// + /// NOTE: Panics if the provided mesh is empty. FIXME: do something else + pub fn create_terrain( + &mut self, + renderer: &mut Renderer, + (tex, tex_size): ColLightInfo, + (opaque, bounds): (Mesh, math::Aabb), + vertex_ranges: [Range; N], + pos: Vec3, + ori: Quaternion, + sprite_instances: [Vec; SPRITE_LOD_LEVELS], + blocks_of_interest: BlocksOfInterest, + ) -> TerrainModelEntry { + span!(_guard, "create_figure", "FigureColLights::create_figure"); + let atlas = &mut self.atlas; + let allocation = atlas + .allocate(guillotiere::Size::new(tex_size.x as i32, tex_size.y as i32)) + .expect("Not yet implemented: allocate new atlas on allocation failure."); + let col_lights = pipelines::shadow::create_col_lights(renderer, &(tex, tex_size)); + let col_lights = renderer.figure_bind_col_light(col_lights); + let model_len = u32::try_from(opaque.vertices().len()) + .expect("The model size for this figure does not fit in a u32!"); + let model = renderer.create_model(&opaque); + + vertex_ranges.iter().for_each(|range| { + assert!( + range.start <= range.end && range.end <= model_len, + "The provided vertex range for figure mesh {:?} does not fit in the model, which \ + is of size {:?}!", + range, + model_len + ); + }); + + let terrain_locals = + renderer.create_terrain_bound_locals(&[TerrainLocals::new(pos, ori, Vec2::zero(), 0.0)]); + + let sprite_instances = + sprite_instances.map(|instances| renderer.create_instances(&instances)); + + TerrainModelEntry { + _bounds: bounds, + allocation, + col_lights, + lod_vertex_ranges: vertex_ranges, + model: FigureModel { opaque: model }, + terrain_locals, + sprite_instances, + blocks_of_interest, + } + } + fn make_atlas(renderer: &mut Renderer) -> Result { let max_texture_size = renderer.max_texture_size(); let atlas_size = guillotiere::Size::new(max_texture_size as i32, max_texture_size as i32); @@ -7061,7 +7319,7 @@ impl FigureState { } } - pub fn update( + pub fn update( &mut self, renderer: &mut Renderer, trail_mgr: Option<&mut TrailMgr>, @@ -7084,7 +7342,7 @@ impl FigureState { ground_vel, }: &FigureUpdateCommonParameters, state_animation_rate: f32, - model: Option<&FigureModelEntry>, + model: Option<&impl ModelEntry>, // TODO: there is the potential to drop the optional body from the common params and just // use this one but we need to add a function to the skelton trait or something in order to // get the rider offset @@ -7152,7 +7410,7 @@ impl FigureState { } }; - let atlas_offs = model.allocation.rectangle.min; + let atlas_offs = model.allocation().rectangle.min; let (light, glow) = terrain .map(|t| { diff --git a/voxygen/src/scene/figure/volume.rs b/voxygen/src/scene/figure/volume.rs index 813592ebb6..1eeb9f7e59 100644 --- a/voxygen/src/scene/figure/volume.rs +++ b/voxygen/src/scene/figure/volume.rs @@ -1,14 +1,9 @@ use super::{ - cache::FigureKey, - load::{BodySpec, BoneMeshes}, + cache::{FigureKey, TerrainModelEntryFuture}, + load::{BodySpec, ShipBoneMeshes}, EcsEntity, }; -use common::{ - assets, - comp::ship::figuredata::VoxelCollider, - figure::{Cell, Segment}, - vol::ReadVol, -}; +use common::{assets, comp::ship::figuredata::VoxelCollider}; use std::{convert::TryFrom, sync::Arc}; #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -68,8 +63,10 @@ impl anim::Skeleton for VolumeKey { } impl BodySpec for VolumeKey { + type BoneMesh = ShipBoneMeshes; type Extra = Arc; type Manifests = (); + type ModelEntryFuture = TerrainModelEntryFuture; type Spec = (); fn load_spec() -> Result { Ok(()) } @@ -82,16 +79,11 @@ impl BodySpec for VolumeKey { _: &FigureKey, _: &Self::Manifests, collider: Self::Extra, - ) -> [Option; anim::MAX_BONE_COUNT] { + ) -> [Option; anim::MAX_BONE_COUNT] { println!("Generating segment..."); [ Some(( - Segment::from_fn(collider.volume().sz, (), |pos| { - match collider.volume().get(pos).unwrap().get_color() { - Some(col) => Cell::new(col, false, false, false), - None => Cell::Empty, - } - }), + collider.volume().clone(), -collider.volume().sz.map(|e| e as f32) / 2.0, )), None, diff --git a/voxygen/src/scene/lod.rs b/voxygen/src/scene/lod.rs index 33800ba15d..57c7b32b02 100644 --- a/voxygen/src/scene/lod.rs +++ b/voxygen/src/scene/lod.rs @@ -138,9 +138,7 @@ impl Lod { .into_iter() .map(|(kind, instances)| { (kind, ObjectGroup { - instances: renderer - .create_instances(&instances) - .expect("Renderer error?!"), + instances: renderer.create_instances(&instances), z_range: z_range.clone(), frustum_last_plane_index: 0, visible: false, diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index af71c314c3..94abb2e3ac 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -1347,14 +1347,28 @@ impl Scene { // Render the skybox. first_pass.draw_skybox(&self.skybox.model); - // Draws translucent terrain and sprites - self.terrain.render_translucent( - &mut first_pass, + // Draws sprites + let mut sprite_drawer = first_pass.draw_sprites( + &self.terrain.sprite_globals, + &self.terrain.sprite_col_lights, + ); + self.figure_mgr.render_sprites( + &mut sprite_drawer, + state, + focus_pos, + scene_data.sprite_render_distance, + ); + self.terrain.render_sprites( + &mut sprite_drawer, focus_pos, cam_pos, scene_data.sprite_render_distance, culling_mode, ); + drop(sprite_drawer); + + // Draws translucent + self.terrain.render_translucent(&mut first_pass, focus_pos); // Render particle effects. self.particle_mgr diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index a3980978c1..d134f9350e 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -1945,9 +1945,7 @@ impl ParticleMgr { .collect::>(); // TODO: optimise buffer writes - let gpu_instances = renderer - .create_instances(&all_cpu_instances) - .expect("Failed to upload particle instances to the GPU!"); + let gpu_instances = renderer.create_instances(&all_cpu_instances); self.instances = gpu_instances; } @@ -1972,9 +1970,7 @@ impl ParticleMgr { fn default_instances(renderer: &mut Renderer) -> Instances { let empty_vec = Vec::new(); - renderer - .create_instances(&empty_vec) - .expect("Failed to upload particle instances to the GPU!") + renderer.create_instances(&empty_vec) } const DEFAULT_MODEL_KEY: &str = "voxygen.voxel.particle"; diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index 8ae9a903e1..6cdca56115 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -1,5 +1,5 @@ use crate::{ - mesh::{greedy::GreedyMesh, segment::generate_mesh_base_vol_terrain}, + mesh::{greedy::GreedyMesh, segment::generate_mesh_base_vol_figure}, render::{ create_skybox_mesh, BoneMeshes, Consts, FigureModel, FirstPassDrawer, GlobalModel, Globals, GlobalsBindGroup, Light, LodData, Mesh, Model, PointLightMatrix, RainOcclusionLocals, @@ -34,6 +34,8 @@ use common::{ use vek::*; use winit::event::MouseButton; +use super::figure::{ModelEntry, ModelEntryRef}; + struct VoidVol; impl BaseVol for VoidVol { type Error = (); @@ -51,7 +53,7 @@ fn generate_mesh( bone_idx: u8, ) -> BoneMeshes { let (opaque, _, /* shadow */ _, bounds) = - generate_mesh_base_vol_terrain(segment, (greedy, mesh, offset, Vec3::one(), bone_idx)); + generate_mesh_base_vol_figure(segment, (greedy, mesh, offset, Vec3::one(), bone_idx)); (opaque /* , shadow */, bounds) } @@ -385,14 +387,22 @@ impl Scene { if let Some((model, figure_state)) = model.zip(self.figure_state.as_ref()) { if let Some(lod) = model.lod_model(0) { - figure_drawer.draw(lod, figure_state.bound(), self.col_lights.texture(model)); + figure_drawer.draw( + lod, + figure_state.bound(), + self.col_lights.texture(ModelEntryRef::Figure(model)), + ); } } } if let Some((model, state)) = &self.backdrop { if let Some(lod) = model.lod_model(0) { - figure_drawer.draw(lod, state.bound(), self.col_lights.texture(model)); + figure_drawer.draw( + lod, + state.bound(), + self.col_lights.texture(ModelEntryRef::Figure(model)), + ); } } drop(figure_drawer); diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index b1e6c072a0..60581402f0 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -10,10 +10,9 @@ use crate::{ }, render::{ pipelines::{self, ColLights}, - AltIndices, ColLightInfo, CullingMode, FirstPassDrawer, FluidVertex, GlobalModel, - Instances, LodData, Mesh, Model, RenderError, Renderer, SpriteGlobalsBindGroup, - SpriteInstance, SpriteVertex, SpriteVerts, TerrainLocals, TerrainShadowDrawer, - TerrainVertex, SPRITE_VERT_PAGE_SIZE, + AltIndices, ColLightInfo, FirstPassDrawer, FluidVertex, GlobalModel, Instances, LodData, Mesh, Model, + RenderError, Renderer, SpriteDrawer, SpriteGlobalsBindGroup, SpriteInstance, SpriteVertex, + SpriteVerts, TerrainLocals, TerrainShadowDrawer, TerrainVertex, SPRITE_VERT_PAGE_SIZE, CullingMode, }, }; @@ -45,7 +44,7 @@ use treeculler::{BVol, Frustum, AABB}; use vek::*; const SPRITE_SCALE: Vec3 = Vec3::new(1.0 / 11.0, 1.0 / 11.0, 1.0 / 11.0); -const SPRITE_LOD_LEVELS: usize = 5; +pub const SPRITE_LOD_LEVELS: usize = 5; // For rain occlusion we only need to render the closest chunks. /// How many chunks are maximally rendered for rain occlusion. @@ -182,7 +181,7 @@ struct SpriteConfig { /// NOTE: Model is an asset path to the appropriate sprite .vox model. #[derive(Deserialize)] #[serde(try_from = "HashMap>>")] -struct SpriteSpec([Option>; 256]); +pub struct SpriteSpec([Option>; 256]); impl SpriteSpec { fn get(&self, kind: SpriteKind) -> Option<&SpriteConfig> { @@ -240,6 +239,70 @@ impl assets::Asset for SpriteSpec { const EXTENSION: &'static str = "ron"; } +pub fn get_sprite_instances<'a, I: 'a>( + lod_levels: &'a mut [I; SPRITE_LOD_LEVELS], + set_instance: impl Fn(&mut I, SpriteInstance, Vec3), + blocks: impl Iterator, Block)>, + mut to_wpos: impl FnMut(Vec3) -> Vec3, + mut light_map: impl FnMut(Vec3) -> f32, + mut glow_map: impl FnMut(Vec3) -> f32, + sprite_data: &HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>, + sprite_config: &SpriteSpec, +) { + prof_span!("extract sprite_instances"); + for (rel_pos, block) in blocks { + let Some(sprite) = block.get_sprite() else { + continue; + }; + + let Some(cfg) = sprite_config.get(sprite) else { + continue; + }; + + let wpos = to_wpos(rel_pos); + let seed = (wpos.x as u64).overflowing_mul(3).0.overflowing_add((wpos.y as u64).overflowing_mul(7).0).0.overflowing_add((wpos.x as u64).overflowing_mul(wpos.y as u64).0).0; // Awful PRNG + + let ori = (block.get_ori().unwrap_or((seed % 4) as u8 * 2)) & 0b111; + let variation = seed as usize % cfg.variations.len(); + let key = (sprite, variation); + + // NOTE: Safe because we called sprite_config_for already. + // NOTE: Safe because 0 ≤ ori < 8 + let light = light_map(wpos); + let glow = glow_map(wpos); + + for (lod_level, sprite_data) in lod_levels.iter_mut().zip(&sprite_data[&key]) { + let mat = Mat4::identity() + // Scaling for different LOD resolutions + .scaled_3d(sprite_data.scale) + // Offset + .translated_3d(sprite_data.offset) + .scaled_3d(SPRITE_SCALE) + .rotated_z(f32::consts::PI * 0.25 * ori as f32) + .translated_3d( + rel_pos + Vec3::new(0.5, 0.5, 0.0) + ); + // Add an instance for each page in the sprite model + for page in sprite_data.vert_pages.clone() { + // TODO: could be more efficient to create once and clone while + // modifying vert_page + let instance = SpriteInstance::new( + mat, + cfg.wind_sway, + sprite_data.scale.z, + rel_pos.as_(), + ori, + light, + glow, + page, + matches!(sprite, SpriteKind::Door), + ); + set_instance(lod_level, instance, wpos); + } + } + } +} + /// Function executed by worker threads dedicated to chunk meshing. /// skip_remesh is either None (do the full remesh, including recomputing the @@ -257,7 +320,12 @@ fn mesh_worker( sprite_config: &SpriteSpec, ) -> MeshWorkerResponse { span!(_guard, "mesh_worker"); - let blocks_of_interest = BlocksOfInterest::from_chunk(&chunk); + let blocks_of_interest = BlocksOfInterest::from_blocks( + chunk.iter_changed().map(|(pos, block)| (pos, *block)), + chunk.meta().river_velocity().magnitude_squared(), + chunk.meta().temp(), + chunk.meta().humidity(), + ); let mesh; let (light_map, glow_map) = if let Some((light_map, glow_map)) = &skip_remesh { @@ -292,7 +360,7 @@ fn mesh_worker( let mesh = mesh.as_ref().unwrap(); (&*mesh.light_map, &*mesh.glow_map) }; - + let to_wpos = |rel_pos: Vec3| Vec3::from(pos * TerrainChunk::RECT_SIZE.map(|e: u32| e as i32)) + rel_pos.as_(); MeshWorkerResponse { pos, // Extract sprite locations from volume @@ -312,77 +380,30 @@ fn mesh_worker( (c.meta().alt() - SHALLOW_ALT, c.meta().alt() - DEEP_ALT) }); - for x in 0..TerrainChunk::RECT_SIZE.x as i32 { - for y in 0..TerrainChunk::RECT_SIZE.y as i32 { - for z in z_bounds.0 as i32..z_bounds.1 as i32 + 1 { - let rel_pos = Vec3::new(x, y, z); - let wpos = Vec3::from(pos * TerrainChunk::RECT_SIZE.map(|e: u32| e as i32)) - + rel_pos; - - let block = if let Ok(block) = volume.get(wpos) { - block - } else { - continue; - }; - let sprite = if let Some(sprite) = block.get_sprite() { - sprite - } else { - continue; - }; - - if let Some(cfg) = sprite_config.get(sprite) { - let seed = wpos.x as u64 * 3 - + wpos.y as u64 * 7 - + wpos.x as u64 * wpos.y as u64; // Awful PRNG - let ori = (block.get_ori().unwrap_or((seed % 4) as u8 * 2)) & 0b111; - let variation = seed as usize % cfg.variations.len(); - let key = (sprite, variation); - // NOTE: Safe because we called sprite_config_for already. - // NOTE: Safe because 0 ≤ ori < 8 - let light = light_map(wpos); - let glow = glow_map(wpos); - - for ((deep_level, shallow_level, surface_level), sprite_data) in - instances.iter_mut().zip(&sprite_data[&key]) - { - let mat = Mat4::identity() - // Scaling for different LOD resolutions - .scaled_3d(sprite_data.scale) - // Offset - .translated_3d(sprite_data.offset) - .scaled_3d(SPRITE_SCALE) - .rotated_z(f32::consts::PI * 0.25 * ori as f32) - .translated_3d( - rel_pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0) - ); - // Add an instance for each page in the sprite model - for page in sprite_data.vert_pages.clone() { - // TODO: could be more efficient to create once and clone while - // modifying vert_page - let instance = SpriteInstance::new( - mat, - cfg.wind_sway, - sprite_data.scale.z, - rel_pos, - ori, - light, - glow, - page, - matches!(sprite, SpriteKind::Door | SpriteKind::DoorDark), - ); - if (wpos.z as f32) < deep_alt { - deep_level.push(instance); - } else if wpos.z as f32 > underground_alt { - surface_level.push(instance); - } else { - shallow_level.push(instance); - } - } - } - } + get_sprite_instances( + &mut instances, + |(deep_level, shallow_level, surface_level), instance, wpos| { + if (wpos.z as f32) < deep_alt { + deep_level.push(instance); + } else if wpos.z as f32 > underground_alt { + surface_level.push(instance); + } else { + shallow_level.push(instance); } - } - } + }, + (0..TerrainChunk::RECT_SIZE.x as i32) + .flat_map(|x| { + (0..TerrainChunk::RECT_SIZE.y as i32).flat_map(move |y| { + (z_bounds.0 as i32..z_bounds.1 as i32).map(move |z| Vec3::new(x, y, z).as_()) + }) + }) + .filter_map(|rel_pos| Some((rel_pos, *volume.get(to_wpos(rel_pos)).ok()?))), + to_wpos, + light_map, + glow_map, + sprite_data, + sprite_config, + ); instances.map(|(deep_level, shallow_level, surface_level)| { let deep_end = deep_level.len(); @@ -406,7 +427,7 @@ fn mesh_worker( } } -struct SpriteData { +pub struct SpriteData { // Sprite vert page ranges that need to be drawn vert_pages: core::ops::Range, // Scale @@ -433,7 +454,7 @@ pub struct Terrain { /// FIXME: This could possibly become an `AssetHandle`, to get /// hot-reloading for free, but I am not sure if sudden changes of this /// value would break something - sprite_config: Arc, + pub sprite_config: Arc, chunks: HashMap, TerrainChunkData>, /// Temporary storage for dead chunks that might still be shadowing chunks /// in view. We wait until either the chunk definitely cannot be @@ -463,9 +484,9 @@ pub struct Terrain { // GPU data // Maps sprite kind + variant to data detailing how to render it - sprite_data: Arc>, - sprite_globals: SpriteGlobalsBindGroup, - sprite_col_lights: Arc>, + pub sprite_data: Arc>, + pub sprite_globals: SpriteGlobalsBindGroup, + pub sprite_col_lights: Arc>, /// As stated previously, this is always the very latest texture into which /// we allocate. Code cannot assume that this is the assigned texture /// for any particular chunk; look at the `texture` field in @@ -1209,8 +1230,7 @@ impl Terrain { response.sprite_instances.map(|(instances, alt_indices)| { ( renderer - .create_instances(&instances) - .expect("Failed to upload chunk sprite instances to the GPU!"), + .create_instances(&instances), alt_indices, ) }); @@ -1282,6 +1302,7 @@ impl Terrain { e as f32 * sz as f32 }), ), + Quaternion::identity(), atlas_offs, load_time, )]), @@ -1694,15 +1715,16 @@ impl Terrain { }); } - pub fn render_translucent<'a>( + pub fn render_sprites<'a>( &'a self, - drawer: &mut FirstPassDrawer<'a>, + sprite_drawer: &mut SpriteDrawer<'_, 'a>, focus_pos: Vec3, cam_pos: Vec3, sprite_render_distance: f32, culling_mode: CullingMode, ) { - span!(_guard, "render_translucent", "Terrain::render_translucent"); + span!(_guard, "render_sprites", "Terrain::render_sprites"); + let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| { (e as i32).div_euclid(sz as i32) }); @@ -1715,9 +1737,6 @@ impl Terrain { }) .take(self.chunks.len()); - // Terrain sprites - // TODO: move to separate functions - span!(guard, "Terrain sprites"); let chunk_size = V::RECT_SIZE.map(|e| e as f32); let sprite_low_detail_distance = sprite_render_distance * 0.75; @@ -1725,7 +1744,6 @@ impl Terrain { let sprite_hid_detail_distance = sprite_render_distance * 0.35; let sprite_high_detail_distance = sprite_render_distance * 0.15; - let mut sprite_drawer = drawer.draw_sprites(&self.sprite_globals, &self.sprite_col_lights); chunk_iter .clone() .filter(|(_, _, c)| c.visible.is_visible()) @@ -1772,15 +1790,32 @@ impl Terrain { ); } }); - drop(sprite_drawer); - drop(guard); + } + + pub fn render_translucent<'a>( + &'a self, + drawer: &mut FirstPassDrawer<'a>, + focus_pos: Vec3, + ) { + span!(_guard, "render_translucent", "Terrain::render_translucent"); + let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| { + (e as i32).div_euclid(sz as i32) + }); + + // Avoid switching textures + let chunk_iter = Spiral2d::new() + .filter_map(|rpos| { + let pos = focus_chunk + rpos; + self.chunks.get(&pos).map(|c| (pos, c)) + }) + .take(self.chunks.len()); // Translucent span!(guard, "Fluid chunks"); let mut fluid_drawer = drawer.draw_fluid(); chunk_iter - .filter(|(_, _, chunk)| chunk.visible.is_visible()) - .filter_map(|(_, _, chunk)| { + .filter(|(_, chunk)| chunk.visible.is_visible()) + .filter_map(|(_, chunk)| { chunk .fluid_model .as_ref() diff --git a/voxygen/src/scene/terrain/watcher.rs b/voxygen/src/scene/terrain/watcher.rs index d003578f79..4b7fa956b4 100644 --- a/voxygen/src/scene/terrain/watcher.rs +++ b/voxygen/src/scene/terrain/watcher.rs @@ -1,7 +1,7 @@ use crate::hud::CraftingTab; use common::{ states::utils::sprite_mount_points, - terrain::{BlockKind, SpriteKind, TerrainChunk}, + terrain::{Block, BlockKind, SpriteKind}, }; use common_base::span; use rand::prelude::*; @@ -61,7 +61,12 @@ pub struct BlocksOfInterest { } impl BlocksOfInterest { - pub fn from_chunk(chunk: &TerrainChunk) -> Self { + pub fn from_blocks( + blocks: impl Iterator, Block)>, + river_speed_sq: f32, + temperature: f32, + humidity: f32, + ) -> Self { span!(_guard, "from_chunk", "BlocksOfInterest::from_chunk"); let mut leaves = Vec::new(); let mut drip = Vec::new(); @@ -88,9 +93,7 @@ impl BlocksOfInterest { let mut rng = ChaCha8Rng::from_seed(thread_rng().gen()); - let river_speed_sq = chunk.meta().river_velocity().magnitude_squared(); - - chunk.iter_changed().for_each(|(pos, block)| { + blocks.for_each(|(pos, block)| { match block.kind() { BlockKind::Leaves if rng.gen_range(0..16) == 0 => leaves.push(pos), BlockKind::WeakRock if rng.gen_range(0..6) == 0 => drip.push(pos), @@ -233,8 +236,8 @@ impl BlocksOfInterest { frogs, interactables, lights, - temperature: chunk.meta().temp(), - humidity: chunk.meta().humidity(), + temperature, + humidity, } } }