use std::collections::HashMap;
use crate::scene::TextureId;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TextureRef {
pub texture: TextureId,
pub uv_set: u32,
pub transform: Option<TextureTransform>,
}
impl TextureRef {
pub fn new(texture: TextureId) -> Self {
Self {
texture,
uv_set: 0,
transform: None,
}
}
pub fn with_uv_set(mut self, uv_set: u32) -> Self {
self.uv_set = uv_set;
self
}
pub fn with_transform(mut self, transform: TextureTransform) -> Self {
self.transform = Some(transform);
self
}
pub fn effective_uv_set(&self) -> u32 {
self.transform.and_then(|t| t.uv_set).unwrap_or(self.uv_set)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TextureTransform {
pub offset: [f32; 2],
pub rotation: f32,
pub scale: [f32; 2],
pub uv_set: Option<u32>,
}
impl TextureTransform {
pub const IDENTITY: Self = Self {
offset: [0.0, 0.0],
rotation: 0.0,
scale: [1.0, 1.0],
uv_set: None,
};
pub fn new() -> Self {
Self::IDENTITY
}
pub fn with_offset(mut self, offset: [f32; 2]) -> Self {
self.offset = offset;
self
}
pub fn with_rotation(mut self, rotation: f32) -> Self {
self.rotation = rotation;
self
}
pub fn with_scale(mut self, scale: [f32; 2]) -> Self {
self.scale = scale;
self
}
pub fn with_uv_set(mut self, uv_set: u32) -> Self {
self.uv_set = Some(uv_set);
self
}
pub fn is_identity(&self) -> bool {
self.offset == [0.0, 0.0]
&& self.rotation == 0.0
&& self.scale == [1.0, 1.0]
&& self.uv_set.is_none()
}
pub fn is_finite(&self) -> bool {
self.offset.iter().all(|c| c.is_finite())
&& self.rotation.is_finite()
&& self.scale.iter().all(|c| c.is_finite())
}
pub fn to_matrix(&self) -> [[f32; 3]; 3] {
let (s, c) = self.rotation.sin_cos();
let [sx, sy] = self.scale;
let [ox, oy] = self.offset;
[[c * sx, -s * sy, ox], [s * sx, c * sy, oy], [0.0, 0.0, 1.0]]
}
pub fn apply(&self, uv: [f32; 2]) -> [f32; 2] {
let (s, c) = self.rotation.sin_cos();
let [sx, sy] = self.scale;
let (u, v) = (uv[0] * sx, uv[1] * sy);
[
c * u - s * v + self.offset[0],
s * u + c * v + self.offset[1],
]
}
pub fn apply_channel(&self, uvs: &[[f32; 2]]) -> Vec<[f32; 2]> {
uvs.iter().map(|&uv| self.apply(uv)).collect()
}
}
impl Default for TextureTransform {
fn default() -> Self {
Self::IDENTITY
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum AlphaMode {
#[default]
Opaque,
Mask { cutoff: f32 },
Blend,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct MaterialExt {
pub emissive_strength: Option<f32>,
pub ior: Option<f32>,
pub specular: Option<Specular>,
pub unlit: bool,
pub clearcoat: Option<Clearcoat>,
pub sheen: Option<Sheen>,
pub transmission: Option<Transmission>,
pub volume: Option<Volume>,
pub iridescence: Option<Iridescence>,
pub anisotropy: Option<Anisotropy>,
pub dispersion: Option<f32>,
pub diffuse_transmission: Option<DiffuseTransmission>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Clearcoat {
pub factor: f32,
pub factor_texture: Option<TextureRef>,
pub roughness: f32,
pub roughness_texture: Option<TextureRef>,
pub normal_texture: Option<TextureRef>,
pub normal_scale: f32,
}
impl Default for Clearcoat {
fn default() -> Self {
Self {
factor: 0.0,
factor_texture: None,
roughness: 0.0,
roughness_texture: None,
normal_texture: None,
normal_scale: 1.0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Sheen {
pub color_factor: [f32; 3],
pub color_texture: Option<TextureRef>,
pub roughness: f32,
pub roughness_texture: Option<TextureRef>,
}
impl Default for Sheen {
fn default() -> Self {
Self {
color_factor: [0.0, 0.0, 0.0],
color_texture: None,
roughness: 0.0,
roughness_texture: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Transmission {
pub factor: f32,
pub factor_texture: Option<TextureRef>,
}
impl Default for Transmission {
fn default() -> Self {
Self {
factor: 0.0,
factor_texture: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Volume {
pub thickness: f32,
pub thickness_texture: Option<TextureRef>,
pub attenuation_distance: Option<f32>,
pub attenuation_color: [f32; 3],
}
impl Volume {
pub fn effective_attenuation_distance(&self) -> f32 {
self.attenuation_distance.unwrap_or(f32::INFINITY)
}
}
impl Default for Volume {
fn default() -> Self {
Self {
thickness: 0.0,
thickness_texture: None,
attenuation_distance: None,
attenuation_color: [1.0, 1.0, 1.0],
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Iridescence {
pub factor: f32,
pub factor_texture: Option<TextureRef>,
pub ior: f32,
pub thickness_min: f32,
pub thickness_max: f32,
pub thickness_texture: Option<TextureRef>,
}
impl Default for Iridescence {
fn default() -> Self {
Self {
factor: 0.0,
factor_texture: None,
ior: 1.3,
thickness_min: 100.0,
thickness_max: 400.0,
thickness_texture: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Anisotropy {
pub strength: f32,
pub rotation: f32,
pub texture: Option<TextureRef>,
}
impl Default for Anisotropy {
fn default() -> Self {
Self {
strength: 0.0,
rotation: 0.0,
texture: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DiffuseTransmission {
pub factor: f32,
pub factor_texture: Option<TextureRef>,
pub color_factor: [f32; 3],
pub color_texture: Option<TextureRef>,
}
impl Default for DiffuseTransmission {
fn default() -> Self {
Self {
factor: 0.0,
factor_texture: None,
color_factor: [1.0, 1.0, 1.0],
color_texture: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Specular {
pub factor: f32,
pub factor_texture: Option<TextureRef>,
pub color_factor: [f32; 3],
pub color_texture: Option<TextureRef>,
}
impl Default for Specular {
fn default() -> Self {
Self {
factor: 1.0,
factor_texture: None,
color_factor: [1.0, 1.0, 1.0],
color_texture: None,
}
}
}
#[derive(Clone, Debug)]
pub struct Material {
pub name: Option<String>,
pub base_color: [f32; 4],
pub base_color_texture: Option<TextureRef>,
pub metallic: f32,
pub roughness: f32,
pub metallic_roughness_texture: Option<TextureRef>,
pub normal_texture: Option<TextureRef>,
pub normal_scale: f32,
pub occlusion_texture: Option<TextureRef>,
pub occlusion_strength: f32,
pub emissive_factor: [f32; 3],
pub emissive_texture: Option<TextureRef>,
pub alpha_mode: AlphaMode,
pub double_sided: bool,
pub ext: MaterialExt,
pub extras: HashMap<String, serde_json::Value>,
}
impl Material {
pub fn new() -> Self {
Self {
name: None,
base_color: [1.0, 1.0, 1.0, 1.0],
base_color_texture: None,
metallic: 1.0,
roughness: 1.0,
metallic_roughness_texture: None,
normal_texture: None,
normal_scale: 1.0,
occlusion_texture: None,
occlusion_strength: 1.0,
emissive_factor: [0.0, 0.0, 0.0],
emissive_texture: None,
alpha_mode: AlphaMode::Opaque,
double_sided: false,
ext: MaterialExt::default(),
extras: HashMap::new(),
}
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn with_base_color(mut self, rgba: [f32; 4]) -> Self {
self.base_color = rgba;
self
}
pub fn with_emissive_strength(mut self, strength: f32) -> Self {
self.ext.emissive_strength = Some(strength);
self
}
pub fn with_ior(mut self, ior: f32) -> Self {
self.ext.ior = Some(ior);
self
}
pub fn with_specular(mut self, specular: Specular) -> Self {
self.ext.specular = Some(specular);
self
}
pub fn with_unlit(mut self, unlit: bool) -> Self {
self.ext.unlit = unlit;
self
}
pub fn with_clearcoat(mut self, clearcoat: Clearcoat) -> Self {
self.ext.clearcoat = Some(clearcoat);
self
}
pub fn with_sheen(mut self, sheen: Sheen) -> Self {
self.ext.sheen = Some(sheen);
self
}
pub fn with_transmission(mut self, transmission: Transmission) -> Self {
self.ext.transmission = Some(transmission);
self
}
pub fn with_volume(mut self, volume: Volume) -> Self {
self.ext.volume = Some(volume);
self
}
pub fn with_iridescence(mut self, iridescence: Iridescence) -> Self {
self.ext.iridescence = Some(iridescence);
self
}
pub fn with_anisotropy(mut self, anisotropy: Anisotropy) -> Self {
self.ext.anisotropy = Some(anisotropy);
self
}
pub fn with_dispersion(mut self, dispersion: f32) -> Self {
self.ext.dispersion = Some(dispersion);
self
}
pub fn with_diffuse_transmission(mut self, diffuse_transmission: DiffuseTransmission) -> Self {
self.ext.diffuse_transmission = Some(diffuse_transmission);
self
}
pub fn effective_emissive_strength(&self) -> f32 {
self.ext.emissive_strength.unwrap_or(1.0)
}
pub fn effective_ior(&self) -> f32 {
self.ext.ior.unwrap_or(1.5)
}
pub fn effective_dispersion(&self) -> f32 {
self.ext.dispersion.unwrap_or(0.0)
}
pub fn dielectric_f0(&self) -> f32 {
let ior = self.effective_ior();
let r = (ior - 1.0) / (ior + 1.0);
r * r
}
pub fn dielectric_f0_rgb(&self) -> [f32; 3] {
let f0 = self.dielectric_f0();
let (factor, color) = match &self.ext.specular {
Some(s) => (s.factor, s.color_factor),
None => (1.0, [1.0, 1.0, 1.0]),
};
color.map(|c| (f0 * c).min(1.0) * factor)
}
pub fn rgb_iors(&self) -> [f32; 3] {
let ior = self.effective_ior();
let dispersion = self.effective_dispersion();
if dispersion == 0.0 || ior < 1.0 {
return [ior; 3];
}
let half_spread = (ior - 1.0) * 0.025 * dispersion;
[(ior - half_spread).max(1.0), ior, ior + half_spread]
}
pub fn texture_refs(&self) -> Vec<(&'static str, TextureRef)> {
let mut out = Vec::new();
let mut push = |name: &'static str, r: &Option<TextureRef>| {
if let Some(tr) = r {
out.push((name, *tr));
}
};
push("base_color_texture", &self.base_color_texture);
push(
"metallic_roughness_texture",
&self.metallic_roughness_texture,
);
push("normal_texture", &self.normal_texture);
push("occlusion_texture", &self.occlusion_texture);
push("emissive_texture", &self.emissive_texture);
if let Some(s) = &self.ext.specular {
push("ext.specular.factor_texture", &s.factor_texture);
push("ext.specular.color_texture", &s.color_texture);
}
if let Some(c) = &self.ext.clearcoat {
push("ext.clearcoat.factor_texture", &c.factor_texture);
push("ext.clearcoat.roughness_texture", &c.roughness_texture);
push("ext.clearcoat.normal_texture", &c.normal_texture);
}
if let Some(sh) = &self.ext.sheen {
push("ext.sheen.color_texture", &sh.color_texture);
push("ext.sheen.roughness_texture", &sh.roughness_texture);
}
if let Some(t) = &self.ext.transmission {
push("ext.transmission.factor_texture", &t.factor_texture);
}
if let Some(dt) = &self.ext.diffuse_transmission {
push(
"ext.diffuse_transmission.factor_texture",
&dt.factor_texture,
);
push("ext.diffuse_transmission.color_texture", &dt.color_texture);
}
if let Some(v) = &self.ext.volume {
push("ext.volume.thickness_texture", &v.thickness_texture);
}
if let Some(ir) = &self.ext.iridescence {
push("ext.iridescence.factor_texture", &ir.factor_texture);
push("ext.iridescence.thickness_texture", &ir.thickness_texture);
}
if let Some(an) = &self.ext.anisotropy {
push("ext.anisotropy.texture", &an.texture);
}
out
}
pub fn map_texture_ids(&mut self, f: impl Fn(crate::TextureId) -> crate::TextureId) {
self.map_texture_refs(|mut r| {
r.texture = f(r.texture);
r
});
}
pub fn map_texture_refs(&mut self, f: impl Fn(TextureRef) -> TextureRef) {
let remap = |r: &mut Option<TextureRef>| {
if let Some(tr) = r {
*tr = f(*tr);
}
};
remap(&mut self.base_color_texture);
remap(&mut self.metallic_roughness_texture);
remap(&mut self.normal_texture);
remap(&mut self.occlusion_texture);
remap(&mut self.emissive_texture);
if let Some(s) = &mut self.ext.specular {
remap(&mut s.factor_texture);
remap(&mut s.color_texture);
}
if let Some(c) = &mut self.ext.clearcoat {
remap(&mut c.factor_texture);
remap(&mut c.roughness_texture);
remap(&mut c.normal_texture);
}
if let Some(sh) = &mut self.ext.sheen {
remap(&mut sh.color_texture);
remap(&mut sh.roughness_texture);
}
if let Some(t) = &mut self.ext.transmission {
remap(&mut t.factor_texture);
}
if let Some(dt) = &mut self.ext.diffuse_transmission {
remap(&mut dt.factor_texture);
remap(&mut dt.color_texture);
}
if let Some(v) = &mut self.ext.volume {
remap(&mut v.thickness_texture);
}
if let Some(ir) = &mut self.ext.iridescence {
remap(&mut ir.factor_texture);
remap(&mut ir.thickness_texture);
}
if let Some(an) = &mut self.ext.anisotropy {
remap(&mut an.texture);
}
}
}
impl Default for Material {
fn default() -> Self {
Self::new()
}
}