use crate::components::Material;
use crate::ecs::{MaterialHandle, TextureHandle};
use crate::gfx::render_types::{MaterialUniforms, NO_ALBEDO_SLOT, NO_NORMAL_MAP_SLOT};
#[derive(Clone, Copy)]
pub(crate) struct MaterialEntry {
pub(crate) albedo_slot: usize,
pub(crate) normal_map_slot: usize,
pub(crate) uniforms: MaterialUniforms,
pub(crate) shader_bucket: u32,
}
pub(crate) fn of(mat: &Material, texture_count: usize) -> Result<MaterialEntry, &'static str> {
let slot_of = |field: &'static str, handle: Option<TextureHandle>, unset: usize| {
let Some(handle) = handle else {
return Ok(unset);
};
let slot = handle.index();
if slot >= texture_count {
return Err(field);
}
Ok(slot)
};
let albedo_slot = slot_of("albedo", mat.albedo, NO_ALBEDO_SLOT)?;
let normal_map_slot = slot_of("normal_map", mat.normal_map, NO_NORMAL_MAP_SLOT)?;
let albedo_secondary_slot = slot_of("albedo_secondary", mat.albedo_secondary, 0)?;
let normal_secondary_slot = slot_of("normal_secondary", mat.normal_secondary, texture_count)?;
let emissive_map_slot = slot_of("emissive_map", mat.emissive_map, 0)?;
let orm_map_slot = slot_of("orm_map", mat.orm_map, 0)?;
Ok(MaterialEntry {
albedo_slot,
normal_map_slot,
uniforms: MaterialUniforms {
roughness: mat.roughness,
metallic: mat.metallic,
macro_variation: mat.macro_variation,
terrain_blend: mat.terrain_blend,
tint: mat.tint,
_pad2: 0.0,
emissive: mat.emissive_factor,
secondary_blend_sharpness: mat.secondary_blend_sharpness,
albedo_secondary_index: albedo_secondary_slot as u32,
normal_secondary_index: normal_secondary_slot as u32,
emissive_map_index: emissive_map_slot as u32,
orm_map_index: orm_map_slot as u32,
alpha_cutoff: mat.alpha_cutoff,
opacity: mat.opacity,
transparent: u32::from(mat.transparent),
see_through: u32::from(mat.see_through),
},
shader_bucket: mat.shader.map_or(0, |h| h.0),
})
}
pub(crate) fn from_texture(texture: Option<TextureHandle>, texture_count: usize) -> MaterialEntry {
let albedo_slot = match texture {
Some(tex_id) if tex_id.index() < texture_count => tex_id.index(),
_ => NO_ALBEDO_SLOT,
};
MaterialEntry {
albedo_slot,
normal_map_slot: NO_NORMAL_MAP_SLOT,
uniforms: MaterialUniforms::DEFAULT,
shader_bucket: 0,
}
}
pub(crate) fn resolve_material_slots(
material: Option<MaterialHandle>,
texture: Option<TextureHandle>,
material_map: &std::collections::HashMap<MaterialHandle, MaterialEntry>,
texture_count: usize,
) -> Result<MaterialEntry, MaterialHandle> {
if let Some(mat_id) = material {
return material_map.get(&mat_id).copied().ok_or(mat_id);
}
Ok(from_texture(texture, texture_count))
}
#[cfg(test)]
mod tests {
use super::*;
fn material() -> Material {
Material {
roughness: 0.25,
metallic: 0.75,
..Default::default()
}
}
#[test]
fn unset_references_take_their_fallbacks() {
let entry = of(&material(), 4).expect("bakes");
assert_eq!(entry.albedo_slot, NO_ALBEDO_SLOT);
assert_eq!(entry.normal_map_slot, NO_NORMAL_MAP_SLOT);
assert_eq!(entry.uniforms.normal_secondary_index, 4);
assert_eq!(entry.uniforms.emissive_map_index, 0);
assert_eq!(entry.uniforms.orm_map_index, 0);
assert_eq!(entry.uniforms.roughness, 0.25);
assert_eq!(entry.uniforms.metallic, 0.75);
}
#[test]
fn a_set_reference_resolves_to_its_pool_slot() {
let mut mat = material();
mat.albedo = Some(TextureHandle(2));
mat.normal_map = Some(TextureHandle(3));
let entry = of(&mat, 4).expect("bakes");
assert_eq!(entry.albedo_slot, 2);
assert_eq!(entry.normal_map_slot, 3);
}
#[test]
fn a_reference_past_the_pool_names_its_field() {
let mut mat = material();
mat.orm_map = Some(TextureHandle(9));
assert_eq!(of(&mat, 4).err(), Some("orm_map"));
}
#[test]
fn the_shader_reference_becomes_the_draw_bucket() {
let mut mat = material();
assert_eq!(of(&mat, 0).expect("bakes").shader_bucket, 0);
mat.shader = Some(concinnity_core::ecs::ShaderHandle(3));
assert_eq!(of(&mat, 0).expect("bakes").shader_bucket, 3);
}
#[test]
fn a_texture_only_draw_takes_the_default_material() {
let entry = from_texture(Some(TextureHandle(1)), 4);
assert_eq!(entry.albedo_slot, 1);
assert_eq!(entry.normal_map_slot, NO_NORMAL_MAP_SLOT);
assert_eq!(entry.shader_bucket, 0);
assert_eq!(
from_texture(Some(TextureHandle(9)), 4).albedo_slot,
NO_ALBEDO_SLOT
);
assert_eq!(from_texture(None, 4).albedo_slot, NO_ALBEDO_SLOT);
}
#[test]
fn a_material_handle_wins_over_the_texture() {
let entry = of(&material(), 4).expect("bakes");
let map = std::collections::HashMap::from([(MaterialHandle(2), entry)]);
let got = resolve_material_slots(Some(MaterialHandle(2)), Some(TextureHandle(1)), &map, 4)
.expect("resolves");
assert_eq!(got.uniforms.roughness, 0.25);
assert_eq!(got.albedo_slot, NO_ALBEDO_SLOT, "the material's own albedo");
assert_eq!(
resolve_material_slots(Some(MaterialHandle(5)), None, &map, 4).err(),
Some(MaterialHandle(5))
);
}
}