sprites on volumes

This commit is contained in:
Isse 2023-02-22 09:52:40 +01:00
parent 458acce5ac
commit fcb7011cde
33 changed files with 1510 additions and 531 deletions

View File

@ -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"),
),
),

Binary file not shown.

Binary file not shown.

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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));

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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.

View File

@ -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;

View File

@ -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)))
}
}

View File

@ -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,

View File

@ -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): (

View File

@ -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,

View File

@ -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],
}
}
}

View File

@ -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

View File

@ -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,

View File

@ -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)])
}
}
}

View File

@ -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,

View File

@ -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| {

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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";

View File

@ -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);

View File

@ -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()

View File

@ -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,
}
}
}