use crate::{Color, Error, Result};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct TextureHandle(pub u64);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct MaterialId(pub u64);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum AlphaMode {
#[default]
Opaque,
AlphaTest,
Blend,
}
#[derive(Clone, Debug, PartialEq)]
pub struct FlatColorMaterial {
pub color: Color,
pub opacity: f32,
}
impl Default for FlatColorMaterial {
fn default() -> Self {
Self {
color: Color::new(1.0, 1.0, 1.0, 1.0),
opacity: 1.0,
}
}
}
impl FlatColorMaterial {
pub fn validate(&self) -> Result<()> {
if self
.color
.to_array()
.iter()
.any(|c| !c.is_finite() || *c < 0.0 || *c > 1.0)
|| !self.opacity.is_finite()
|| self.opacity < 0.0
|| self.opacity > 1.0
{
return Err(Error::invalid_argument(
"render::FlatColorMaterial color and opacity must be finite and in [0, 1]",
));
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct UnlitMaterial {
pub base_color_texture: Option<TextureHandle>,
pub tint: Color,
pub uv_scale: [f32; 2],
pub uv_offset: [f32; 2],
pub alpha_mode: AlphaMode,
}
impl Default for UnlitMaterial {
fn default() -> Self {
Self {
base_color_texture: None,
tint: Color::new(1.0, 1.0, 1.0, 1.0),
uv_scale: [1.0, 1.0],
uv_offset: [0.0, 0.0],
alpha_mode: AlphaMode::Opaque,
}
}
}
impl UnlitMaterial {
pub fn validate(&self) -> Result<()> {
if self.base_color_texture.is_some_and(|handle| handle.0 == 0)
|| self.tint.to_array().iter().any(|c| !c.is_finite())
|| self.uv_scale.iter().any(|c| !c.is_finite())
|| self.uv_offset.iter().any(|c| !c.is_finite())
{
return Err(Error::invalid_argument(
"render::UnlitMaterial tint, uv_scale, and uv_offset must be finite",
));
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct StandardSurfaceMaterial {
pub base_color: [f32; 3],
pub base_color_texture: Option<TextureHandle>,
pub metalness: f32,
pub roughness: f32,
pub orm_texture: Option<TextureHandle>,
pub normal_texture: Option<TextureHandle>,
pub normal_scale: f32,
pub specular_ior: f32,
pub specular_tint: [f32; 3],
pub emission_color: [f32; 3],
pub emission_scale: f32,
pub emission_texture: Option<TextureHandle>,
pub coat_weight: f32,
pub coat_roughness: f32,
pub coat_ior: f32,
pub coat_normal_texture: Option<TextureHandle>,
pub sheen_weight: f32,
pub sheen_color: [f32; 3],
pub sheen_roughness: f32,
pub subsurface_weight: f32,
pub subsurface_color: [f32; 3],
pub subsurface_radius: [f32; 3],
pub opacity: f32,
pub opacity_texture: Option<TextureHandle>,
pub alpha_mode: AlphaMode,
pub alpha_cutoff: f32,
pub uv_scale: [f32; 2],
pub uv_offset: [f32; 2],
}
impl Default for StandardSurfaceMaterial {
fn default() -> Self {
Self {
base_color: [0.8, 0.8, 0.8],
base_color_texture: None,
metalness: 0.0,
roughness: 0.5,
orm_texture: None,
normal_texture: None,
normal_scale: 1.0,
specular_ior: 1.5,
specular_tint: [1.0, 1.0, 1.0],
emission_color: [0.0, 0.0, 0.0],
emission_scale: 0.0,
emission_texture: None,
coat_weight: 0.0,
coat_roughness: 0.03,
coat_ior: 1.5,
coat_normal_texture: None,
sheen_weight: 0.0,
sheen_color: [1.0, 1.0, 1.0],
sheen_roughness: 0.3,
subsurface_weight: 0.0,
subsurface_color: [1.0, 1.0, 1.0],
subsurface_radius: [1.0, 1.0, 1.0],
opacity: 1.0,
opacity_texture: None,
alpha_mode: AlphaMode::Opaque,
alpha_cutoff: 0.5,
uv_scale: [1.0, 1.0],
uv_offset: [0.0, 0.0],
}
}
}
impl StandardSurfaceMaterial {
pub fn validate(&self) -> Result<()> {
let in_unit = |v: f32| v.is_finite() && (0.0..=1.0).contains(&v);
let finite3 = |c: [f32; 3]| c.iter().all(|v| v.is_finite());
let finite2 = |c: [f32; 2]| c.iter().all(|v| v.is_finite());
let texture_handles = [
self.base_color_texture,
self.orm_texture,
self.normal_texture,
self.emission_texture,
self.coat_normal_texture,
self.opacity_texture,
];
if texture_handles
.into_iter()
.flatten()
.any(|handle| handle.0 == 0)
|| !finite3(self.base_color)
|| !in_unit(self.metalness)
|| !in_unit(self.roughness)
|| !self.normal_scale.is_finite()
|| !self.specular_ior.is_finite()
|| self.specular_ior <= 0.0
|| !finite3(self.specular_tint)
|| !finite3(self.emission_color)
|| !self.emission_scale.is_finite()
|| self.emission_scale < 0.0
|| !in_unit(self.coat_weight)
|| !in_unit(self.coat_roughness)
|| !self.coat_ior.is_finite()
|| self.coat_ior <= 0.0
|| !in_unit(self.sheen_weight)
|| !finite3(self.sheen_color)
|| !in_unit(self.sheen_roughness)
|| !in_unit(self.subsurface_weight)
|| !finite3(self.subsurface_color)
|| !finite3(self.subsurface_radius)
|| self.subsurface_radius.iter().any(|v| *v < 0.0)
|| !in_unit(self.opacity)
|| !in_unit(self.alpha_cutoff)
|| !finite2(self.uv_scale)
|| !finite2(self.uv_offset)
{
return Err(Error::invalid_argument(
"render::StandardSurfaceMaterial parameters must be finite and in-range",
));
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Material {
FlatColor(FlatColorMaterial),
Unlit(UnlitMaterial),
Standard(StandardSurfaceMaterial),
}
impl Default for Material {
fn default() -> Self {
Self::Standard(StandardSurfaceMaterial::default())
}
}
impl Material {
pub fn validate(&self) -> Result<()> {
match self {
Self::FlatColor(m) => m.validate(),
Self::Unlit(m) => m.validate(),
Self::Standard(m) => m.validate(),
}
}
}