mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
sprites on volumes
This commit is contained in:
parent
458acce5ac
commit
fcb7011cde
@ -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"),
|
||||
),
|
||||
),
|
||||
|
BIN
assets/common/voxel/air_balloon/structure.vox
(Stored with Git LFS)
BIN
assets/common/voxel/air_balloon/structure.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/common/voxel/airship_human/structure.vox
(Stored with Git LFS)
BIN
assets/common/voxel/airship_human/structure.vox
(Stored with Git LFS)
Binary file not shown.
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<super::Body, SidedShipCentralVoxSpec>);
|
||||
|
||||
#[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<u8>) -> 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<u8, DeBlock>,
|
||||
}
|
||||
|
||||
#[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<Block, (), ColumnAccess>,
|
||||
pub(super) dyna: TerrainSegment,
|
||||
pub translation: Vec3<f32>,
|
||||
/// 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<F: FnMut(Vec3<i32>) -> Block>(sz: Vec3<u32>, 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<Block, (), ColumnAccess> { &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::<DotVoxAsset>(&["common.voxel.", &bone.central.0].concat())?;
|
||||
let dyna = Dyna::<Cell, (), ColumnAccess>::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);
|
||||
|
@ -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<Block, ()>;
|
||||
|
||||
impl From<Segment> 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.
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<i32>,
|
||||
base: Arc<BaseStructure>,
|
||||
base: Arc<BaseStructure<StructureBlock>>,
|
||||
custom_indices: [Option<StructureBlock>; 256],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BaseStructure {
|
||||
vol: Dyna<Option<NonZeroU8>, ()>,
|
||||
palette: [StructureBlock; 256],
|
||||
pub(crate) struct BaseStructure<B> {
|
||||
pub(crate) vol: Dyna<Option<NonZeroU8>, ()>,
|
||||
pub(crate) palette: [B; 256],
|
||||
}
|
||||
|
||||
pub struct StructuresGroup(Vec<Structure>);
|
||||
@ -79,7 +86,7 @@ impl assets::Compound for StructuresGroup {
|
||||
.0
|
||||
.iter()
|
||||
.map(|sp| {
|
||||
let base = cache.load::<Arc<BaseStructure>>(&sp.specifier)?.cloned();
|
||||
let base = cache.load::<Arc<BaseStructure<StructureBlock>>>(&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<B: Default>(dot_vox_data: &DotVoxData, mut to_block: impl FnMut(Rgb<u8>) -> B) -> BaseStructure<B> {
|
||||
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<StructureBlock> {
|
||||
fn load(cache: assets::AnyCache, specifier: &assets::SharedString) -> Result<Self, BoxedError> {
|
||||
let dot_vox_data = cache.load::<DotVoxAsset>(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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,6 +241,7 @@ where
|
||||
|
||||
/// Convenience iterator type that can be used to quickly implement
|
||||
/// `IntoPosIterator`.
|
||||
#[derive(Clone)]
|
||||
pub struct DefaultPosIterator {
|
||||
current: Vec3<i32>,
|
||||
begin: Vec2<i32>,
|
||||
@ -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,
|
||||
|
@ -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<TerrainVertex>,
|
||||
Vec3<f32>,
|
||||
Vec3<f32>,
|
||||
u8,
|
||||
),
|
||||
) -> MeshGen<TerrainVertex, TerrainVertex, TerrainVertex, math::Aabb<f32>>
|
||||
where
|
||||
V: BaseVol<Vox = Block> + 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_::<usize>();
|
||||
let greedy_size_cross = greedy_size;
|
||||
let draw_delta = lower_bound;
|
||||
|
||||
let get_light = |vol: &mut V, pos: Vec3<i32>| {
|
||||
if vol.get(pos).map_or(true, |vox| vox.is_fluid()) {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
let get_ao = |vol: &mut V, pos: Vec3<i32>| {
|
||||
if vol.get(pos).map_or(false, |vox| vox.is_opaque()) {
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
};
|
||||
let get_glow = |vol: &mut V, pos: Vec3<i32>| {
|
||||
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<i32>| vol.get(pos).map_or(true, |vox| vox.is_fluid());
|
||||
let should_draw = |vol: &mut V, pos: Vec3<i32>, delta: Vec3<i32>, _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_::<f32>() + offs) * scale),
|
||||
max: math::Vec3::from((upper_bound.as_::<f32>() + 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): (
|
||||
|
@ -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<i32>,
|
||||
delta: Vec3<i32>,
|
||||
flat_get: impl Fn(Vec3<i32>) -> Block,
|
||||
|
@ -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<f32>, atlas_offs: Vec2<u32>, load_time: f32) -> Self {
|
||||
pub fn new(model_offs: Vec3<f32>, ori: Quaternion<f32>, atlas_offs: Vec2<u32>, 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],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1266,13 +1266,10 @@ impl Renderer {
|
||||
}
|
||||
|
||||
/// Create a new set of instances with the provided values.
|
||||
pub fn create_instances<T: Copy + bytemuck::Pod>(
|
||||
&mut self,
|
||||
vals: &[T],
|
||||
) -> Result<Instances<T>, RenderError> {
|
||||
pub fn create_instances<T: Copy + bytemuck::Pod>(&mut self, vals: &[T]) -> Instances<T> {
|
||||
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
|
||||
|
@ -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<T>,
|
||||
instances: &'data Instances<sprite::Instance>,
|
||||
alt_indices: &'data AltIndices,
|
||||
culling_mode: CullingMode,
|
||||
|
@ -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<const N: usize> {
|
||||
pub struct MeshWorkerResponse<const N: usize> {
|
||||
col_light: ColLightInfo,
|
||||
opaque: Mesh<TerrainVertex>,
|
||||
bounds: anim::vek::Aabb<f32>,
|
||||
vertex_range: [Range<u32>; N],
|
||||
}
|
||||
|
||||
/// A type produced by mesh worker threads corresponding to the information
|
||||
/// needed to mesh figures.
|
||||
pub struct TerrainMeshWorkerResponse<const N: usize> {
|
||||
col_light: ColLightInfo,
|
||||
opaque: Mesh<TerrainVertex>,
|
||||
bounds: anim::vek::Aabb<f32>,
|
||||
vertex_range: [Range<u32>; N],
|
||||
sprite_instances: [Vec<SpriteInstance>; 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<const N: usize> = atomic::AtomicCell<Option<MeshWorkerResponse<N>>>;
|
||||
pub type MeshWorkerCell<const N: usize> = atomic::AtomicCell<Option<MeshWorkerResponse<N>>>;
|
||||
pub type TerrainMeshWorkerCell<const N: usize> =
|
||||
atomic::AtomicCell<Option<TerrainMeshWorkerResponse<N>>>;
|
||||
|
||||
pub trait ModelEntryFuture<const N: usize> {
|
||||
type ModelEntry: ModelEntry;
|
||||
|
||||
fn into_done(self) -> Option<Self::ModelEntry>;
|
||||
|
||||
fn get_done(&self) -> Option<&Self::ModelEntry>;
|
||||
}
|
||||
|
||||
/// A future FigureModelEntryLod.
|
||||
enum FigureModelEntryFuture<const N: usize> {
|
||||
pub enum FigureModelEntryFuture<const N: usize> {
|
||||
/// 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<const N: usize> {
|
||||
Done(FigureModelEntry<N>),
|
||||
}
|
||||
|
||||
impl<const N: usize> ModelEntryFuture<N> for FigureModelEntryFuture<N> {
|
||||
type ModelEntry = FigureModelEntry<N>;
|
||||
|
||||
fn into_done(self) -> Option<Self::ModelEntry> {
|
||||
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<const N: usize> {
|
||||
/// 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<TerrainMeshWorkerCell<N>>),
|
||||
/// Stores the already-meshed model.
|
||||
Done(TerrainModelEntry<N>),
|
||||
}
|
||||
|
||||
impl<const N: usize> ModelEntryFuture<N> for TerrainModelEntryFuture<N> {
|
||||
type ModelEntry = TerrainModelEntry<N>;
|
||||
|
||||
fn into_done(self) -> Option<Self::ModelEntry> {
|
||||
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<LOD_COUNT>>;
|
||||
type TerrainModelEntryLod<'b> = Option<&'b TerrainModelEntry<LOD_COUNT>>;
|
||||
|
||||
#[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<FigureKey<Skel::Body>, ((FigureModelEntryFuture<LOD_COUNT>, Skel::Attr), u64)>,
|
||||
models: HashMap<
|
||||
FigureKey<Skel::Body>,
|
||||
(
|
||||
(
|
||||
<Skel::Body as BodySpec>::ModelEntryFuture<LOD_COUNT>,
|
||||
Skel::Attr,
|
||||
),
|
||||
u64,
|
||||
),
|
||||
>,
|
||||
manifests: <Skel::Body as BodySpec>::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<ItemKey>,
|
||||
) -> FigureModelEntryLod<'b> {
|
||||
) -> Option<&<<Skel::Body as BodySpec>::ModelEntryFuture<LOD_COUNT> as ModelEntryFuture<LOD_COUNT>>::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
|
||||
<Skel::Body as BodySpec>::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<Skel: Skeleton> FigureModelCache<Skel>
|
||||
where
|
||||
Skel::Body: BodySpec<
|
||||
BoneMesh = super::load::BoneMeshes,
|
||||
ModelEntryFuture<LOD_COUNT> = FigureModelEntryFuture<LOD_COUNT>,
|
||||
> + Eq
|
||||
+ Hash,
|
||||
{
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn get_or_create_model<'c>(
|
||||
&'c mut self,
|
||||
@ -408,7 +529,7 @@ where
|
||||
offset: Vec3<f32>,
|
||||
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<Skel: Skeleton> FigureModelCache<Skel>
|
||||
where
|
||||
Skel::Body: BodySpec<
|
||||
BoneMesh = ShipBoneMeshes,
|
||||
ModelEntryFuture<LOD_COUNT> = TerrainModelEntryFuture<LOD_COUNT>,
|
||||
> + 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<f32>,
|
||||
ori: Quaternion<f32>,
|
||||
body: Skel::Body,
|
||||
extra: <Skel::Body as BodySpec>::Extra,
|
||||
tick: u64,
|
||||
slow_jobs: &SlowJobPool,
|
||||
terrain: &Terrain,
|
||||
) -> (TerrainModelEntryLod<'c>, &'c Skel::Attr)
|
||||
where
|
||||
<Skel::Body as BodySpec>::Spec: Clone,
|
||||
for<'a> &'a Skel::Body: Into<Skel::Attr>,
|
||||
Skel::Body: Clone + Send + Sync + 'static,
|
||||
<Skel::Body as BodySpec>::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 =
|
||||
<Skel::Body as BodySpec>::bone_meshes(&key, &manifests, extra);
|
||||
|
||||
// Then, set up meshing context.
|
||||
let mut greedy = FigureModel::make_greedy();
|
||||
let mut opaque = Mesh::<TerrainVertex>::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<TerrainVertex>,
|
||||
segment: &'a TerrainSegment,
|
||||
offset: Vec3<f32>,
|
||||
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<TerrainVertex>,
|
||||
segment: &'a TerrainSegment,
|
||||
offset: Vec3<f32>,
|
||||
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<TerrainVertex>,
|
||||
segment: &'a TerrainSegment,
|
||||
offset: Vec3<f32>,
|
||||
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<SpriteInstance>; 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<f32>,
|
||||
ori: Quaternion<f32>,
|
||||
) {
|
||||
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)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<const N: usize>: ModelEntryFuture<N>;
|
||||
|
||||
/// Initialize all the specifications for this Body.
|
||||
fn load_spec() -> Result<Self::Manifests, assets::Error>;
|
||||
@ -126,7 +128,7 @@ pub trait BodySpec: Sized {
|
||||
key: &FigureKey<Self>,
|
||||
manifests: &Self::Manifests,
|
||||
extra: Self::Extra,
|
||||
) -> [Option<BoneMeshes>; anim::MAX_BONE_COUNT];
|
||||
) -> [Option<Self::BoneMesh>; anim::MAX_BONE_COUNT];
|
||||
}
|
||||
|
||||
macro_rules! make_vox_spec {
|
||||
@ -152,6 +154,8 @@ macro_rules! make_vox_spec {
|
||||
type Spec = $Spec;
|
||||
type Manifests = AssetHandle<Self::Spec>;
|
||||
type Extra = ();
|
||||
type BoneMesh = BoneMeshes;
|
||||
type ModelEntryFuture<const N: usize> = FigureModelEntryFuture<N>;
|
||||
|
||||
fn load_spec() -> Result<Self::Manifests, assets::Error> {
|
||||
Self::Spec::load("")
|
||||
@ -5258,31 +5262,32 @@ fn segment_center(segment: &Segment) -> Option<Vec3<f32>> {
|
||||
}
|
||||
}
|
||||
|
||||
fn mesh_ship_bone<K: fmt::Debug + Eq + Hash, V, F: Fn(&V) -> &ShipCentralSubSpec>(
|
||||
pub type ShipBoneMeshes = (Dyna<Block, ()>, Vec3<f32>);
|
||||
|
||||
fn mesh_ship_bone<'a, K: fmt::Debug + Eq + Hash, V, F: Fn(&V) -> Option<&'a VoxelCollider>>(
|
||||
map: &HashMap<K, V>,
|
||||
obj: &K,
|
||||
f: F,
|
||||
) -> BoneMeshes {
|
||||
) -> Option<ShipBoneMeshes> {
|
||||
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<Self::Spec>;
|
||||
type Spec = ShipSpec;
|
||||
type BoneMesh = ShipBoneMeshes;
|
||||
type ModelEntryFuture<const N: usize> = TerrainModelEntryFuture<N>;
|
||||
|
||||
fn load_spec() -> Result<Self::Manifests, assets::Error> { Self::Spec::load("") }
|
||||
|
||||
@ -5292,14 +5297,15 @@ impl BodySpec for ship::Body {
|
||||
FigureKey { body, .. }: &FigureKey<Self>,
|
||||
manifests: &Self::Manifests,
|
||||
_: Self::Extra,
|
||||
) -> [Option<BoneMeshes>; anim::MAX_BONE_COUNT] {
|
||||
let spec = &*manifests.read();
|
||||
let map = &(spec.central.read().0).0;
|
||||
) -> [Option<Self::BoneMesh>; 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,
|
||||
|
@ -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<pipelines::figure::Locals>,
|
||||
);
|
||||
|
||||
pub trait ModelEntry {
|
||||
fn allocation(&self) -> &guillotiere::Allocation;
|
||||
|
||||
fn lod_model(&self, lod: usize) -> Option<SubModel<TerrainVertex>>;
|
||||
|
||||
fn col_lights(&self) -> &ColLights<pipelines::figure::Locals>;
|
||||
}
|
||||
|
||||
/// An entry holding enough information to draw or destroy a figure in a
|
||||
/// particular cache.
|
||||
pub struct FigureModelEntry<const N: usize> {
|
||||
@ -95,14 +110,79 @@ pub struct FigureModelEntry<const N: usize> {
|
||||
model: FigureModel,
|
||||
}
|
||||
|
||||
impl<const N: usize> FigureModelEntry<N> {
|
||||
pub fn lod_model(&self, lod: usize) -> Option<SubModel<TerrainVertex>> {
|
||||
impl<const N: usize> ModelEntry for FigureModelEntry<N> {
|
||||
fn allocation(&self) -> &guillotiere::Allocation { &self.allocation }
|
||||
|
||||
fn lod_model(&self, lod: usize) -> Option<SubModel<TerrainVertex>> {
|
||||
// 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<pipelines::figure::Locals> { &self.col_lights }
|
||||
}
|
||||
|
||||
/// An entry holding enough information to draw or destroy a figure in a
|
||||
/// particular cache.
|
||||
pub struct TerrainModelEntry<const N: usize> {
|
||||
/// The estimated bounds of this figure, in voxels. This may not be very
|
||||
/// useful yet.
|
||||
_bounds: math::Aabb<f32>,
|
||||
/// 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<pipelines::figure::Locals>,
|
||||
/// Vertex ranges stored in this figure entry; there may be several for one
|
||||
/// figure, because of LOD models.
|
||||
lod_vertex_ranges: [Range<u32>; N],
|
||||
model: FigureModel,
|
||||
|
||||
terrain_locals: BoundTerrainLocals,
|
||||
sprite_instances: [Instances<SpriteInstance>; SPRITE_LOD_LEVELS],
|
||||
|
||||
blocks_of_interest: BlocksOfInterest,
|
||||
}
|
||||
|
||||
impl<const N: usize> ModelEntry for TerrainModelEntry<N> {
|
||||
fn allocation(&self) -> &guillotiere::Allocation { &self.allocation }
|
||||
|
||||
fn lod_model(&self, lod: usize) -> Option<SubModel<TerrainVertex>> {
|
||||
// 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<pipelines::figure::Locals> { &self.col_lights }
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ModelEntryRef<'a, const N: usize> {
|
||||
Figure(&'a FigureModelEntry<N>),
|
||||
Terrain(&'a TerrainModelEntry<N>),
|
||||
}
|
||||
|
||||
impl<'a, const N: usize> ModelEntryRef<'a, N> {
|
||||
fn lod_model(&self, lod: usize) -> Option<SubModel<'a, TerrainVertex>> {
|
||||
match self {
|
||||
ModelEntryRef::Figure(e) => e.lod_model(lod),
|
||||
ModelEntryRef::Terrain(e) => e.lod_model(lod),
|
||||
}
|
||||
}
|
||||
|
||||
fn col_lights(&self) -> &'a ColLights<pipelines::figure::Locals> {
|
||||
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<f32>,
|
||||
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::<Pos>(),
|
||||
&ecs.read_storage::<Body>(),
|
||||
ecs.read_storage::<Health>().maybe(),
|
||||
ecs.read_storage::<Inventory>().maybe(),
|
||||
ecs.read_storage::<Scale>().maybe(),
|
||||
ecs.read_storage::<Collider>().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::<CharacterState>();
|
||||
let character_state = character_state_storage.get(player_entity);
|
||||
let character_state = character_state_storage.get(viewpoint_entity);
|
||||
let items = ecs.read_storage::<Item>();
|
||||
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::<CharacterState>();
|
||||
let character_state = character_state_storage.get(player_entity);
|
||||
let character_state = character_state_storage.get(viewpoint_entity);
|
||||
let items = ecs.read_storage::<Item>();
|
||||
|
||||
if let (Some(pos), Some(body), scale) = (
|
||||
ecs.read_storage::<Pos>().get(player_entity),
|
||||
ecs.read_storage::<Body>().get(player_entity),
|
||||
ecs.read_storage::<Scale>().get(player_entity),
|
||||
ecs.read_storage::<Pos>().get(viewpoint_entity),
|
||||
ecs.read_storage::<Body>().get(viewpoint_entity),
|
||||
ecs.read_storage::<Scale>().get(viewpoint_entity),
|
||||
) {
|
||||
let healths = state.read_storage::<Health>();
|
||||
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::<Inventory>();
|
||||
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<SpriteInstance>; 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<f32> {
|
||||
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<N>,
|
||||
model: ModelEntryRef<'a, N>,
|
||||
) -> &'a ColLights<pipelines::figure::Locals> {
|
||||
/* &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<const N: usize>(
|
||||
&mut self,
|
||||
renderer: &mut Renderer,
|
||||
(tex, tex_size): ColLightInfo,
|
||||
(opaque, bounds): (Mesh<TerrainVertex>, math::Aabb<f32>),
|
||||
vertex_ranges: [Range<u32>; N],
|
||||
pos: Vec3<f32>,
|
||||
ori: Quaternion<f32>,
|
||||
sprite_instances: [Vec<SpriteInstance>; SPRITE_LOD_LEVELS],
|
||||
blocks_of_interest: BlocksOfInterest,
|
||||
) -> TerrainModelEntry<N> {
|
||||
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<AtlasAllocator, RenderError> {
|
||||
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<S: Skeleton> FigureState<S> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update<const N: usize>(
|
||||
pub fn update(
|
||||
&mut self,
|
||||
renderer: &mut Renderer,
|
||||
trail_mgr: Option<&mut TrailMgr>,
|
||||
@ -7084,7 +7342,7 @@ impl<S: Skeleton> FigureState<S> {
|
||||
ground_vel,
|
||||
}: &FigureUpdateCommonParameters,
|
||||
state_animation_rate: f32,
|
||||
model: Option<&FigureModelEntry<N>>,
|
||||
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<S: Skeleton> FigureState<S> {
|
||||
}
|
||||
};
|
||||
|
||||
let atlas_offs = model.allocation.rectangle.min;
|
||||
let atlas_offs = model.allocation().rectangle.min;
|
||||
|
||||
let (light, glow) = terrain
|
||||
.map(|t| {
|
||||
|
@ -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<VoxelCollider>;
|
||||
type Manifests = ();
|
||||
type ModelEntryFuture<const N: usize> = TerrainModelEntryFuture<N>;
|
||||
type Spec = ();
|
||||
|
||||
fn load_spec() -> Result<Self::Manifests, assets::Error> { Ok(()) }
|
||||
@ -82,16 +79,11 @@ impl BodySpec for VolumeKey {
|
||||
_: &FigureKey<Self>,
|
||||
_: &Self::Manifests,
|
||||
collider: Self::Extra,
|
||||
) -> [Option<BoneMeshes>; anim::MAX_BONE_COUNT] {
|
||||
) -> [Option<Self::BoneMesh>; 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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -1945,9 +1945,7 @@ impl ParticleMgr {
|
||||
.collect::<Vec<ParticleInstance>>();
|
||||
|
||||
// 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<ParticleInstance> {
|
||||
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";
|
||||
|
@ -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);
|
||||
|
@ -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<f32> = 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<Model> {
|
||||
/// NOTE: Model is an asset path to the appropriate sprite .vox model.
|
||||
#[derive(Deserialize)]
|
||||
#[serde(try_from = "HashMap<SpriteKind, Option<SpriteConfig<String>>>")]
|
||||
struct SpriteSpec([Option<SpriteConfig<String>>; 256]);
|
||||
pub struct SpriteSpec([Option<SpriteConfig<String>>; 256]);
|
||||
|
||||
impl SpriteSpec {
|
||||
fn get(&self, kind: SpriteKind) -> Option<&SpriteConfig<String>> {
|
||||
@ -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<i32>),
|
||||
blocks: impl Iterator<Item = (Vec3<f32>, Block)>,
|
||||
mut to_wpos: impl FnMut(Vec3<f32>) -> Vec3<i32>,
|
||||
mut light_map: impl FnMut(Vec3<i32>) -> f32,
|
||||
mut glow_map: impl FnMut(Vec3<i32>) -> 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<f32>| 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<u32>,
|
||||
// Scale
|
||||
@ -433,7 +454,7 @@ pub struct Terrain<V: RectRasterableVol = TerrainChunk> {
|
||||
/// FIXME: This could possibly become an `AssetHandle<SpriteSpec>`, to get
|
||||
/// hot-reloading for free, but I am not sure if sudden changes of this
|
||||
/// value would break something
|
||||
sprite_config: Arc<SpriteSpec>,
|
||||
pub sprite_config: Arc<SpriteSpec>,
|
||||
chunks: HashMap<Vec2<i32>, 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<V: RectRasterableVol = TerrainChunk> {
|
||||
|
||||
// GPU data
|
||||
// Maps sprite kind + variant to data detailing how to render it
|
||||
sprite_data: Arc<HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>>,
|
||||
sprite_globals: SpriteGlobalsBindGroup,
|
||||
sprite_col_lights: Arc<ColLights<pipelines::sprite::Locals>>,
|
||||
pub sprite_data: Arc<HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>>,
|
||||
pub sprite_globals: SpriteGlobalsBindGroup,
|
||||
pub sprite_col_lights: Arc<ColLights<pipelines::sprite::Locals>>,
|
||||
/// 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<V: RectRasterableVol> Terrain<V> {
|
||||
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<V: RectRasterableVol> Terrain<V> {
|
||||
e as f32 * sz as f32
|
||||
}),
|
||||
),
|
||||
Quaternion::identity(),
|
||||
atlas_offs,
|
||||
load_time,
|
||||
)]),
|
||||
@ -1694,15 +1715,16 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn render_translucent<'a>(
|
||||
pub fn render_sprites<'a>(
|
||||
&'a self,
|
||||
drawer: &mut FirstPassDrawer<'a>,
|
||||
sprite_drawer: &mut SpriteDrawer<'_, 'a>,
|
||||
focus_pos: Vec3<f32>,
|
||||
cam_pos: Vec3<f32>,
|
||||
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<V: RectRasterableVol> Terrain<V> {
|
||||
})
|
||||
.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<V: RectRasterableVol> Terrain<V> {
|
||||
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<V: RectRasterableVol> Terrain<V> {
|
||||
);
|
||||
}
|
||||
});
|
||||
drop(sprite_drawer);
|
||||
drop(guard);
|
||||
}
|
||||
|
||||
pub fn render_translucent<'a>(
|
||||
&'a self,
|
||||
drawer: &mut FirstPassDrawer<'a>,
|
||||
focus_pos: Vec3<f32>,
|
||||
) {
|
||||
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()
|
||||
|
@ -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<Item = (Vec3<i32>, 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user