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}