Skip to main content

pfx_scene/types/
material.rs

1use serde::{Deserialize, Serialize};
2
3use crate::Id;
4
5pub const LAYER_CAP: usize = 4;
6pub const PARAMS: usize = 12;
7
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum Family {
11    #[default]
12    Plain,
13    Paper,
14    Plaster,
15    Stone,
16    Cloth,
17    Metal,
18    Occluder,
19    Petal,
20    Leaf,
21    Bark,
22    Cord,
23    Wood,
24    Lacquer,
25    Glass,
26    Liquid,
27    Emissive,
28    Chrome,
29}
30
31#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(rename_all = "snake_case")]
33pub enum Content {
34    #[default]
35    None,
36    Ink,
37    Decal,
38    Screen,
39    Photo,
40    Scroll,
41    Print,
42    Field,
43}
44
45#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case")]
47pub enum NormalSource {
48    #[default]
49    Flat,
50    Bump,
51    Map,
52}
53
54#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(rename_all = "snake_case")]
56pub enum NoiseKind {
57    #[default]
58    None,
59    Fibre,
60    Crinkle,
61    PlankWood,
62    WallMottle,
63    Grime,
64    Leaf,
65    Bark,
66    Flow,
67    Value,
68    Fbm,
69    Scratch,
70    CoatWobble,
71}
72
73#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
74#[serde(rename_all = "snake_case")]
75pub enum Blend {
76    #[default]
77    Over,
78    Multiply,
79    Emit,
80}
81
82fn unset(params: &[f32; PARAMS]) -> bool {
83    params.iter().all(|value| *value == 0.0)
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
87#[serde(deny_unknown_fields)]
88pub struct NoiseLayer {
89    pub kind: NoiseKind,
90    pub frequency: f32,
91    pub amplitude: f32,
92    pub seed: u32,
93    #[serde(default, skip_serializing_if = "unset")]
94    pub params: [f32; PARAMS],
95}
96
97impl Default for NoiseLayer {
98    fn default() -> Self {
99        Self {
100            kind: NoiseKind::None,
101            frequency: 1.0,
102            amplitude: 0.0,
103            seed: 0,
104            params: [0.0; PARAMS],
105        }
106    }
107}
108
109#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
110#[serde(deny_unknown_fields)]
111pub struct Normal {
112    pub source: NormalSource,
113    pub strength: f32,
114}
115
116impl Default for Normal {
117    fn default() -> Self {
118        Self {
119            source: NormalSource::Flat,
120            strength: 0.0,
121        }
122    }
123}
124
125#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
126#[serde(deny_unknown_fields)]
127pub struct Maps {
128    pub layer: f32,
129    pub tile: f32,
130    pub normal: f32,
131    pub albedo: f32,
132}
133
134impl Default for Maps {
135    fn default() -> Self {
136        Self {
137            layer: -1.0,
138            tile: 1.0,
139            normal: 1.0,
140            albedo: 1.0,
141        }
142    }
143}
144
145#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
146#[serde(deny_unknown_fields)]
147pub struct Ageing {
148    pub fade: f32,
149    pub yellow: f32,
150    pub ink: f32,
151    pub scratch: f32,
152    pub edge: f32,
153    pub dust: f32,
154    pub patina: f32,
155    pub seed: u32,
156}
157
158#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
159#[serde(default, deny_unknown_fields)]
160pub struct ContentLayer {
161    pub slot: i32,
162    pub blend: Blend,
163    pub ink_roughness: f32,
164    pub emboss: f32,
165    pub strength: f32,
166}
167
168impl Default for ContentLayer {
169    fn default() -> Self {
170        Self {
171            slot: -1,
172            blend: Blend::Over,
173            ink_roughness: -1.0,
174            emboss: 0.0,
175            strength: 1.0,
176        }
177    }
178}
179
180#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
181#[serde(default, deny_unknown_fields)]
182pub struct Material {
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub id: Option<Id>,
185    pub family: Family,
186    pub base: [f32; 3],
187    pub roughness: f32,
188    pub metalness: f32,
189    pub specular: f32,
190    pub clearcoat: f32,
191    pub clearcoat_roughness: f32,
192    pub sheen: f32,
193    pub transmission: f32,
194    pub ior: f32,
195    pub dispersion: f32,
196    pub thickness: f32,
197    pub subsurface: f32,
198    pub subsurface_tint: [f32; 3],
199    pub absorption: f32,
200    pub thin_film: f32,
201    pub thin_film_ior: f32,
202    pub thin_film_amount: f32,
203    pub emission: [f32; 3],
204    pub fresnel_power: f32,
205    pub normal: Normal,
206    pub maps: Maps,
207    pub content: Content,
208    pub content_layer: ContentLayer,
209    #[serde(skip_serializing_if = "Vec::is_empty")]
210    pub layers: Vec<NoiseLayer>,
211    pub ageing: Ageing,
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub authoring: Option<toml::Value>,
214}
215
216impl Default for Material {
217    fn default() -> Self {
218        Self {
219            id: None,
220            family: Family::Plain,
221            base: [0.5; 3],
222            roughness: 0.5,
223            metalness: 0.0,
224            specular: 0.04,
225            clearcoat: 0.0,
226            clearcoat_roughness: 0.05,
227            sheen: 0.0,
228            transmission: 0.0,
229            ior: 1.5,
230            dispersion: 0.0,
231            thickness: 0.0,
232            subsurface: 0.0,
233            subsurface_tint: [1.0; 3],
234            absorption: 0.0,
235            thin_film: 0.0,
236            thin_film_ior: 1.5,
237            thin_film_amount: 0.0,
238            emission: [0.0; 3],
239            fresnel_power: 5.0,
240            normal: Normal::default(),
241            maps: Maps::default(),
242            content: Content::None,
243            content_layer: ContentLayer::default(),
244            layers: Vec::new(),
245            ageing: Ageing::default(),
246            authoring: None,
247        }
248    }
249}
250
251impl Material {
252    pub fn to_table(&self) -> toml::Table {
253        let base = Material {
254            id: self.id,
255            authoring: self.authoring.clone(),
256            ..Material::default()
257        };
258        let full = toml::Table::try_from(self).unwrap_or_default();
259        let plain = toml::Table::try_from(&base).unwrap_or_default();
260        full.into_iter()
261            .filter(|(key, value)| {
262                key == "id" || key == "authoring" || plain.get(key) != Some(value)
263            })
264            .collect()
265    }
266}