Define different sprite configurations per-attributes

This commit is contained in:
crabman 2024-03-03 22:48:17 +00:00
parent bedb46ac48
commit 4781134d7e
No known key found for this signature in database
3 changed files with 708 additions and 630 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
pub mod sprite;
mod watcher; mod watcher;
use self::sprite::SpriteSpec;
pub use self::watcher::{BlocksOfInterest, FireplaceType, Interaction}; pub use self::watcher::{BlocksOfInterest, FireplaceType, Interaction};
use crate::{ use crate::{
@ -15,6 +17,7 @@ use crate::{
SpriteGlobalsBindGroup, SpriteInstance, SpriteVertex, SpriteVerts, TerrainAtlasData, SpriteGlobalsBindGroup, SpriteInstance, SpriteVertex, SpriteVerts, TerrainAtlasData,
TerrainLocals, TerrainShadowDrawer, TerrainVertex, SPRITE_VERT_PAGE_SIZE, TerrainLocals, TerrainShadowDrawer, TerrainVertex, SPRITE_VERT_PAGE_SIZE,
}, },
scene::terrain::sprite::SpriteModelConfig,
}; };
use super::{ use super::{
@ -22,7 +25,7 @@ use super::{
math, SceneData, RAIN_THRESHOLD, math, SceneData, RAIN_THRESHOLD,
}; };
use common::{ use common::{
assets::{self, AssetExt, DotVoxAsset}, assets::{AssetExt, DotVoxAsset},
figure::Segment, figure::Segment,
spiral::Spiral2d, spiral::Spiral2d,
terrain::{Block, SpriteKind, TerrainChunk}, terrain::{Block, SpriteKind, TerrainChunk},
@ -34,7 +37,6 @@ use core::{f32, fmt::Debug, marker::PhantomData, time::Duration};
use crossbeam_channel as channel; use crossbeam_channel as channel;
use guillotiere::AtlasAllocator; use guillotiere::AtlasAllocator;
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::Deserialize;
use std::sync::{ use std::sync::{
atomic::{AtomicU64, Ordering}, atomic::{AtomicU64, Ordering},
Arc, Arc,
@ -151,49 +153,6 @@ struct MeshWorkerResponse {
blocks_of_interest: BlocksOfInterest, blocks_of_interest: BlocksOfInterest,
} }
#[derive(Deserialize)]
/// Configuration data for an individual sprite model.
struct SpriteModelConfig<Model> {
/// Data for the .vox model associated with this sprite.
model: Model,
/// Sprite model center (as an offset from 0 in the .vox file).
offset: (f32, f32, f32),
/// LOD axes (how LOD gets applied along each axis, when we switch
/// to an LOD model).
lod_axes: (f32, f32, f32),
}
#[derive(Deserialize)]
/// Configuration data for a group of sprites (currently associated with a
/// particular SpriteKind).
struct SpriteConfig<Model> {
/// All possible model variations for this sprite.
// NOTE: Could make constant per sprite type, but eliminating this indirection and
// allocation is probably not that important considering how sprites are used.
variations: Vec<SpriteModelConfig<Model>>,
/// The extent to which the sprite sways in the window.
///
/// 0.0 is normal.
wind_sway: f32,
}
// TODO: reduce llvm IR lines from this
/// Configuration data for all sprite models.
///
/// NOTE: Model is an asset path to the appropriate sprite .vox model.
// TODO: Overhaul this entirely to work with the new sprite attribute system. We'll probably be
// wanting a way to specify inexact mappings between sprite models and sprite configurations. For
// example, the ability to use a model for a range of plant growth states.
#[derive(Deserialize)]
#[serde(try_from = "HashMap<SpriteKind, Option<SpriteConfig<String>>>")]
pub struct SpriteSpec(HashMap<SpriteKind, Option<SpriteConfig<String>>>);
impl SpriteSpec {
fn get(&self, kind: SpriteKind) -> Option<&SpriteConfig<String>> {
self.0.get(&kind).and_then(Option::as_ref)
}
}
/// Conversion of SpriteSpec from a hashmap failed because some sprites were /// Conversion of SpriteSpec from a hashmap failed because some sprites were
/// missing. /// missing.
struct SpritesMissing(Vec<SpriteKind>); struct SpritesMissing(Vec<SpriteKind>);
@ -210,42 +169,6 @@ impl fmt::Display for SpritesMissing {
} }
} }
// Here we ensure all variants have an entry in the config.
impl TryFrom<HashMap<SpriteKind, Option<SpriteConfig<String>>>> for SpriteSpec {
type Error = SpritesMissing;
fn try_from(
map: HashMap<SpriteKind, Option<SpriteConfig<String>>>,
) -> Result<Self, Self::Error> {
Ok(Self(map))
/*
let mut array = [(); 65536].map(|()| None);
let sprites_missing = SpriteKind::iter()
.filter(|kind| match map.remove(kind) {
Some(config) => {
array[*kind as usize] = config;
false
},
None => true,
})
.collect::<Vec<_>>();
if sprites_missing.is_empty() {
Ok(Self(array))
} else {
Err(SpritesMissing(sprites_missing))
}
*/
}
}
impl assets::Asset for SpriteSpec {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
pub fn get_sprite_instances<'a, I: 'a>( pub fn get_sprite_instances<'a, I: 'a>(
lod_levels: &'a mut [I; SPRITE_LOD_LEVELS], lod_levels: &'a mut [I; SPRITE_LOD_LEVELS],
set_instance: impl Fn(&mut I, SpriteInstance, Vec3<i32>), set_instance: impl Fn(&mut I, SpriteInstance, Vec3<i32>),
@ -253,7 +176,7 @@ pub fn get_sprite_instances<'a, I: 'a>(
mut to_wpos: impl FnMut(Vec3<f32>) -> Vec3<i32>, mut to_wpos: impl FnMut(Vec3<f32>) -> Vec3<i32>,
mut light_map: impl FnMut(Vec3<i32>) -> f32, mut light_map: impl FnMut(Vec3<i32>) -> f32,
mut glow_map: impl FnMut(Vec3<i32>) -> f32, mut glow_map: impl FnMut(Vec3<i32>) -> f32,
sprite_data: &HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>, sprite_data: &HashMap<(SpriteKind, usize, usize), [SpriteData; SPRITE_LOD_LEVELS]>,
sprite_config: &SpriteSpec, sprite_config: &SpriteSpec,
) { ) {
prof_span!("extract sprite_instances"); prof_span!("extract sprite_instances");
@ -262,7 +185,7 @@ pub fn get_sprite_instances<'a, I: 'a>(
continue; continue;
}; };
let Some(cfg) = sprite_config.get(sprite) else { let Some((cfg_i, cfg)) = sprite_config.get_for_block(&block) else {
continue; continue;
}; };
@ -287,7 +210,7 @@ pub fn get_sprite_instances<'a, I: 'a>(
4 => (((seed.overflowing_add(wpos.x as u64).0) as usize % 7) + 1) / 2, 4 => (((seed.overflowing_add(wpos.x as u64).0) as usize % 7) + 1) / 2,
_ => seed as usize % cfg.variations.len(), _ => seed as usize % cfg.variations.len(),
}; };
let key = (sprite, variation); let key = (sprite, variation, cfg_i);
// NOTE: Safe because we called sprite_config_for already. // NOTE: Safe because we called sprite_config_for already.
// NOTE: Safe because 0 ≤ ori < 8 // NOTE: Safe because 0 ≤ ori < 8
@ -340,7 +263,7 @@ fn mesh_worker(
max_texture_size: u16, max_texture_size: u16,
chunk: Arc<TerrainChunk>, chunk: Arc<TerrainChunk>,
range: Aabb<i32>, range: Aabb<i32>,
sprite_data: &HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>, sprite_data: &HashMap<(SpriteKind, usize, usize), [SpriteData; SPRITE_LOD_LEVELS]>,
sprite_config: &SpriteSpec, sprite_config: &SpriteSpec,
) -> MeshWorkerResponse { ) -> MeshWorkerResponse {
span!(_guard, "mesh_worker"); span!(_guard, "mesh_worker");
@ -539,7 +462,7 @@ pub struct SpriteRenderState {
/// value would break something /// value would break something
pub sprite_config: Arc<SpriteSpec>, pub sprite_config: Arc<SpriteSpec>,
// Maps sprite kind + variant to data detailing how to render it // Maps sprite kind + variant to data detailing how to render it
pub sprite_data: Arc<HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>>, pub sprite_data: Arc<HashMap<(SpriteKind, usize, usize), [SpriteData; SPRITE_LOD_LEVELS]>>,
pub sprite_atlas_textures: Arc<AtlasTextures<pipelines::sprite::Locals, FigureSpriteAtlasData>>, pub sprite_atlas_textures: Arc<AtlasTextures<pipelines::sprite::Locals, FigureSpriteAtlasData>>,
} }
@ -557,7 +480,7 @@ impl SpriteRenderContext {
struct SpriteWorkerResponse { struct SpriteWorkerResponse {
sprite_config: Arc<SpriteSpec>, sprite_config: Arc<SpriteSpec>,
sprite_data: HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>, sprite_data: HashMap<(SpriteKind, usize, usize), [SpriteData; SPRITE_LOD_LEVELS]>,
sprite_atlas_texture_data: FigureSpriteAtlasData, sprite_atlas_texture_data: FigureSpriteAtlasData,
sprite_atlas_size: Vec2<u16>, sprite_atlas_size: Vec2<u16>,
sprite_mesh: Mesh<SpriteVertex>, sprite_mesh: Mesh<SpriteVertex>,
@ -576,9 +499,14 @@ impl SpriteRenderContext {
); );
let mut sprite_mesh = Mesh::new(); let mut sprite_mesh = Mesh::new();
// NOTE: Tracks the start vertex of the next model to be meshed. // NOTE: Tracks the start vertex of the next model to be meshed.
let sprite_data: HashMap<(SpriteKind, usize), _> = SpriteKind::iter() let sprite_data: HashMap<(SpriteKind, usize, usize), _> = SpriteKind::iter()
.filter_map(|kind| Some((kind, sprite_config.get(kind)?))) .flat_map(|kind| {
.flat_map(|(kind, sprite_config)| { sprite_config
.get(kind)
.enumerate()
.map(move |(i, (v, _))| (kind, v, i))
})
.flat_map(|(kind, sprite_config, filter_variant)| {
sprite_config.variations.iter().enumerate().map( sprite_config.variations.iter().enumerate().map(
move |( move |(
variation, variation,
@ -661,7 +589,7 @@ impl SpriteRenderContext {
} }
}); });
((kind, variation), lod_sprite_data) ((kind, variation, filter_variant), lod_sprite_data)
} }
}, },
) )

View File

@ -0,0 +1,150 @@
use std::ops::Range;
use common::{
assets,
terrain::{sprite, Block, SpriteKind},
};
use hashbrown::HashMap;
use serde::Deserialize;
#[derive(Deserialize, Debug)]
/// Configuration data for an individual sprite model.
pub struct SpriteModelConfig<Model> {
/// Data for the .vox model associated with this sprite.
pub model: Model,
/// Sprite model center (as an offset from 0 in the .vox file).
pub offset: (f32, f32, f32),
/// LOD axes (how LOD gets applied along each axis, when we switch
/// to an LOD model).
pub lod_axes: (f32, f32, f32),
}
#[derive(Deserialize, Debug)]
/// Configuration data for a group of sprites (currently associated with a
/// particular SpriteKind).
pub struct SpriteConfig<Model> {
/// All possible model variations for this sprite.
// NOTE: Could make constant per sprite type, but eliminating this indirection and
// allocation is probably not that important considering how sprites are used.
pub variations: Vec<SpriteModelConfig<Model>>,
/// The extent to which the sprite sways in the window.
///
/// 0.0 is normal.
pub wind_sway: f32,
}
// TODO: reduce llvm IR lines from this
/// Configuration data for all sprite models.
///
/// NOTE: Model is an asset path to the appropriate sprite .vox model.
#[derive(Deserialize)]
pub struct SpriteSpec(HashMap<(SpriteKind, SpriteAttributeFilters), Option<SpriteConfig<String>>>);
macro_rules! impl_sprite_attribute_filter {
(
$(#[$meta:meta])*
$vis:vis struct $n:ident {
$($attr:ident $field_name:ident = |$filter_arg:ident: $filter_ty:ty, $value_arg:ident| $filter:block),+
}
) => {
$(#[$meta])*
$vis struct $n {
$(
pub $field_name: Option<$filter_ty>,
)+
}
impl $n {
fn sprite_attribute_score(&self, block: &Block) -> Option<usize> {
if $(
self.$field_name.as_ref().map_or(true, |$filter_arg| {
block
.get_attr::<sprite::$attr>()
.map_or(false, |$value_arg| $filter)
})
)&&+ {
Some(
[$(self.$field_name.is_some()),+]
.into_iter()
.filter(|o| *o)
.count(),
)
} else {
None
}
}
#[cfg(test)]
fn is_valid_for_category(&self, category: sprite::Category) -> Result<(), &'static str> {
$(if self.$field_name.is_some() && !category.has_attr::<sprite::$attr>() {
return Err(::std::any::type_name::<sprite::$attr>());
})*
Ok(())
}
}
};
}
impl_sprite_attribute_filter!(
#[derive(Debug, Clone, Deserialize, Default, PartialEq, Eq, Hash)]
#[serde(default)]
pub struct SpriteAttributeFilters {
Growth growth_stage = |filter: Range<u8>, growth| { filter.contains(&growth.0) },
LightEnabled light_enabled = |filter: bool, light_enabled| { *filter == light_enabled.0 }
}
);
impl assets::Asset for SpriteSpec {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
impl SpriteSpec {
pub fn get(
&self,
kind: SpriteKind,
) -> impl Iterator<Item = (&SpriteConfig<String>, &SpriteAttributeFilters)> + '_ {
self.0
.iter()
.filter_map(move |((sprite_kind, filters), v)| {
(*sprite_kind == kind).then_some((v.as_ref()?, filters))
})
}
pub fn get_for_block(&self, block: &Block) -> Option<(usize, &SpriteConfig<String>)> {
let sprite = block.get_sprite()?;
self.get(sprite)
.enumerate()
.filter_map(|(cfg_i, (cfg, filter))| {
Some((cfg_i, cfg, filter.sprite_attribute_score(block)?))
})
.max_by_key(|(_, _, score)| *score)
.map(|(cfg_i, cfg, _)| (cfg_i, cfg))
}
}
#[cfg(test)]
mod test {
use common_assets::AssetExt;
use super::SpriteSpec;
#[test]
fn test_sprite_spec_valid() {
let spec = SpriteSpec::load_expect("voxygen.voxel.sprite_manifest").read();
for (sprite, filter) in spec.0.keys() {
if let Err(invalid_attribute) = filter.is_valid_for_category(sprite.category()) {
panic!(
"Sprite category '{:?}' does not have attribute '{}' (in sprite config for \
{:?})",
sprite.category(),
invalid_attribute,
sprite,
);
}
}
}
}