1use serde::{Deserialize, Serialize};
2
3use crate::content::ContentLayer;
4
5pub const LAYER_CAP: usize = 4;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum Family {
10 #[default]
11 Plain,
12 Paper,
13 Plaster,
14 Stone,
15 Cloth,
16 Metal,
17 Occluder,
18 Petal,
19 Leaf,
20 Bark,
21 Cord,
22 Wood,
23 Lacquer,
24 Glass,
25 Liquid,
26 Emissive,
27 Chrome,
28}
29
30impl Family {
31 fn id(self) -> u32 {
32 match self {
33 Self::Plain => 0,
34 Self::Paper => 1,
35 Self::Plaster => 2,
36 Self::Stone => 3,
37 Self::Cloth => 4,
38 Self::Metal => 5,
39 Self::Occluder => 7,
40 Self::Petal => 8,
41 Self::Leaf => 9,
42 Self::Bark => 10,
43 Self::Cord => 11,
44 Self::Wood => 12,
45 Self::Lacquer => 13,
46 Self::Glass => 14,
47 Self::Liquid => 15,
48 Self::Emissive => 17,
49 Self::Chrome => 18,
50 }
51 }
52
53 fn from_id(id: u32) -> Self {
54 match id {
55 1 => Self::Paper,
56 2 => Self::Plaster,
57 3 => Self::Stone,
58 4 => Self::Cloth,
59 5 => Self::Metal,
60 7 => Self::Occluder,
61 8 => Self::Petal,
62 9 => Self::Leaf,
63 10 => Self::Bark,
64 11 => Self::Cord,
65 12 => Self::Wood,
66 13 => Self::Lacquer,
67 14 => Self::Glass,
68 15 => Self::Liquid,
69 17 => Self::Emissive,
70 18 => Self::Chrome,
71 _ => Self::Plain,
72 }
73 }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
77#[serde(rename_all = "snake_case")]
78pub enum Content {
79 #[default]
80 None,
81 Ink,
82 Decal,
83 Screen,
84 Photo,
85 Scroll,
86 Print,
87 Field,
88}
89
90impl Content {
91 fn id(self) -> u32 {
92 match self {
93 Self::None => 0,
94 Self::Ink => 1,
95 Self::Decal => 2,
96 Self::Screen => 3,
97 Self::Photo => 4,
98 Self::Scroll => 5,
99 Self::Print => 6,
100 Self::Field => 7,
101 }
102 }
103
104 fn from_id(id: u32) -> Self {
105 match id {
106 1 => Self::Ink,
107 2 => Self::Decal,
108 3 => Self::Screen,
109 4 => Self::Photo,
110 5 => Self::Scroll,
111 6 => Self::Print,
112 7 => Self::Field,
113 _ => Self::None,
114 }
115 }
116}
117
118#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
119#[serde(rename_all = "snake_case")]
120pub enum NormalSource {
121 #[default]
122 Flat,
123 Bump,
124 Map,
125}
126
127impl NormalSource {
128 fn id(self) -> u32 {
129 match self {
130 Self::Flat => 0,
131 Self::Bump => 1,
132 Self::Map => 2,
133 }
134 }
135
136 fn from_id(id: u32) -> Self {
137 match id {
138 1 => Self::Bump,
139 2 => Self::Map,
140 _ => Self::Flat,
141 }
142 }
143}
144
145#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
146#[serde(rename_all = "snake_case")]
147pub enum NoiseKind {
148 #[default]
149 None,
150 Fibre,
151 Crinkle,
152 PlankWood,
153 WallMottle,
154 Grime,
155 Leaf,
156 Bark,
157 Flow,
158 Value,
159 Fbm,
160 Scratch,
161 CoatWobble,
162}
163
164impl NoiseKind {
165 fn id(self) -> u32 {
166 match self {
167 Self::None => 0,
168 Self::Fibre => 1,
169 Self::Crinkle => 2,
170 Self::PlankWood => 3,
171 Self::WallMottle => 4,
172 Self::Grime => 5,
173 Self::Leaf => 7,
174 Self::Bark => 8,
175 Self::Flow => 14,
176 Self::Value => 15,
177 Self::Fbm => 16,
178 Self::Scratch => 17,
179 Self::CoatWobble => 18,
180 }
181 }
182
183 fn from_id(id: u32) -> Self {
184 match id {
185 1 => Self::Fibre,
186 2 => Self::Crinkle,
187 3 => Self::PlankWood,
188 4 => Self::WallMottle,
189 5 => Self::Grime,
190 7 => Self::Leaf,
191 8 => Self::Bark,
192 14 => Self::Flow,
193 15 => Self::Value,
194 16 => Self::Fbm,
195 17 => Self::Scratch,
196 18 => Self::CoatWobble,
197 _ => Self::None,
198 }
199 }
200}
201
202#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
203#[serde(deny_unknown_fields)]
204pub struct NoiseLayer {
205 pub kind: NoiseKind,
206 pub frequency: f32,
207 pub amplitude: f32,
208 pub seed: u32,
209 #[serde(default, skip_serializing_if = "unset")]
210 pub params: [f32; PARAMS],
211}
212
213pub const PARAMS: usize = 12;
214pub const GPU_PARAMS: usize = PARAMS + 4;
215
216pub const SCRATCH_STRETCH: f32 = 0.02;
217
218pub fn gpu_params(layer: &NoiseLayer) -> [f32; GPU_PARAMS] {
219 let p = layer.params;
220 let mut out = [0.0; GPU_PARAMS];
221 out[..PARAMS].copy_from_slice(&p);
222 match layer.kind {
223 NoiseKind::PlankWood if p[0] > 0.0 => {
224 out[PARAMS] = 1.0 / p[0];
225 out[PARAMS + 1] = 1.0 - p[7];
226 }
227 NoiseKind::Scratch => out[PARAMS] = p[0] * SCRATCH_STRETCH,
228 _ => {}
229 }
230 out
231}
232
233fn unset(params: &[f32; PARAMS]) -> bool {
234 params.iter().all(|value| *value == 0.0)
235}
236
237impl Default for NoiseLayer {
238 fn default() -> Self {
239 Self {
240 kind: NoiseKind::None,
241 frequency: 1.0,
242 amplitude: 0.0,
243 seed: 0,
244 params: [0.0; PARAMS],
245 }
246 }
247}
248
249impl NoiseLayer {
250 pub fn new(kind: NoiseKind, frequency: f32, amplitude: f32, seed: u32) -> Self {
251 Self {
252 kind,
253 frequency,
254 amplitude,
255 seed,
256 params: [0.0; PARAMS],
257 }
258 }
259
260 pub fn with_params(self, params: [f32; PARAMS]) -> Self {
261 Self { params, ..self }
262 }
263
264 pub fn active(self) -> bool {
265 self.kind != NoiseKind::None
266 && self.amplitude != 0.0
267 && (!matches!(self.kind, NoiseKind::PlankWood | NoiseKind::CoatWobble)
268 || self.params[0] > 0.0)
269 }
270}
271
272#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
273#[serde(deny_unknown_fields)]
274pub struct Normal {
275 pub source: NormalSource,
276 pub strength: f32,
277}
278
279impl Default for Normal {
280 fn default() -> Self {
281 Self {
282 source: NormalSource::Flat,
283 strength: 0.0,
284 }
285 }
286}
287
288#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
289#[serde(deny_unknown_fields)]
290pub struct Maps {
291 pub layer: f32,
292 pub tile: f32,
293 pub normal: f32,
294 pub albedo: f32,
295}
296
297impl Default for Maps {
298 fn default() -> Self {
299 Self {
300 layer: -1.0,
301 tile: 1.0,
302 normal: 1.0,
303 albedo: 1.0,
304 }
305 }
306}
307
308#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
309#[serde(deny_unknown_fields)]
310pub struct Ageing {
311 pub fade: f32,
312 pub yellow: f32,
313 pub ink: f32,
314 pub scratch: f32,
315 pub edge: f32,
316 pub dust: f32,
317 pub patina: f32,
318 pub seed: u32,
319}
320
321impl Default for Ageing {
322 fn default() -> Self {
323 Self {
324 fade: 0.0,
325 yellow: 0.0,
326 ink: 0.0,
327 scratch: 0.0,
328 edge: 0.0,
329 dust: 0.0,
330 patina: 0.0,
331 seed: 0,
332 }
333 }
334}
335
336impl Ageing {
337 pub fn inactive(self) -> bool {
338 self.fade == 0.0
339 && self.yellow == 0.0
340 && self.ink == 0.0
341 && self.scratch == 0.0
342 && self.edge == 0.0
343 && self.dust == 0.0
344 && self.patina == 0.0
345 }
346}
347
348#[derive(Clone, Copy, Debug, PartialEq)]
349pub struct At {
350 pub position: [f32; 3],
351 pub uv: [f32; 2],
352 pub normal: [f32; 3],
353 pub edge: f32,
354 pub time: f32,
355}
356
357impl Default for At {
358 fn default() -> Self {
359 Self {
360 position: [0.0; 3],
361 uv: [0.0; 2],
362 normal: [0.0, 0.0, 1.0],
363 edge: 0.0,
364 time: 0.0,
365 }
366 }
367}
368
369#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
370#[serde(default, deny_unknown_fields)]
371pub struct Material {
372 pub family: Family,
373 pub base: [f32; 3],
374 pub roughness: f32,
375 pub metalness: f32,
376 pub specular: f32,
377 pub clearcoat: f32,
378 pub clearcoat_roughness: f32,
379 pub sheen: f32,
380 pub transmission: f32,
381 pub ior: f32,
382 pub dispersion: f32,
383 pub thickness: f32,
384 pub subsurface: f32,
385 pub subsurface_tint: [f32; 3],
386 pub absorption: f32,
387 pub thin_film: f32,
388 pub thin_film_ior: f32,
389 pub thin_film_amount: f32,
390 pub emission: [f32; 3],
391 pub fresnel_power: f32,
392 pub normal: Normal,
393 pub maps: Maps,
394 pub content: Content,
395 pub content_layer: ContentLayer,
396 pub layers: [NoiseLayer; LAYER_CAP],
397 pub ageing: Ageing,
398}
399
400impl Default for Material {
401 fn default() -> Self {
402 Self {
403 family: Family::Plain,
404 base: [0.5, 0.5, 0.5],
405 roughness: 0.5,
406 metalness: 0.0,
407 specular: 0.04,
408 clearcoat: 0.0,
409 clearcoat_roughness: 0.05,
410 sheen: 0.0,
411 transmission: 0.0,
412 ior: 1.5,
413 dispersion: 0.0,
414 thickness: 0.0,
415 subsurface: 0.0,
416 subsurface_tint: [1.0, 1.0, 1.0],
417 absorption: 0.0,
418 thin_film: 0.0,
419 thin_film_ior: 1.5,
420 thin_film_amount: 0.0,
421 emission: [0.0; 3],
422 fresnel_power: 5.0,
423 normal: Normal::default(),
424 maps: Maps::default(),
425 content: Content::None,
426 content_layer: ContentLayer::default(),
427 layers: [NoiseLayer::default(); LAYER_CAP],
428 ageing: Ageing::default(),
429 }
430 }
431}
432
433impl Material {
434 pub fn push_layer(&mut self, layer: NoiseLayer) {
435 if let Some(slot) = self
436 .layers
437 .iter_mut()
438 .find(|layer| layer.kind == NoiseKind::None)
439 {
440 *slot = layer;
441 }
442 }
443
444 pub fn to_toml(&self) -> Result<String, String> {
445 let recipe = Recipe::from(*self);
446 toml::to_string(&recipe).map_err(|err| err.to_string())
447 }
448
449 pub fn from_toml(text: &str) -> Result<Self, String> {
450 let recipe: Recipe = toml::from_str(text).map_err(|err| err.to_string())?;
451 recipe.into_material()
452 }
453}
454
455#[derive(Serialize, Deserialize)]
456#[serde(default, deny_unknown_fields)]
457pub(crate) struct Recipe {
458 family: Family,
459 base: [f32; 3],
460 roughness: f32,
461 metalness: f32,
462 specular: f32,
463 clearcoat: f32,
464 clearcoat_roughness: f32,
465 sheen: f32,
466 transmission: f32,
467 ior: f32,
468 dispersion: f32,
469 thickness: f32,
470 subsurface: f32,
471 subsurface_tint: [f32; 3],
472 absorption: f32,
473 thin_film: f32,
474 thin_film_ior: f32,
475 thin_film_amount: f32,
476 emission: [f32; 3],
477 fresnel_power: f32,
478 normal: Normal,
479 maps: Maps,
480 content: Content,
481 content_layer: ContentLayer,
482 layers: Vec<NoiseLayer>,
483 ageing: Ageing,
484}
485
486impl Default for Recipe {
487 fn default() -> Self {
488 Self::from(Material::default())
489 }
490}
491
492impl From<Material> for Recipe {
493 fn from(m: Material) -> Self {
494 Self {
495 family: m.family,
496 base: m.base,
497 roughness: m.roughness,
498 metalness: m.metalness,
499 specular: m.specular,
500 clearcoat: m.clearcoat,
501 clearcoat_roughness: m.clearcoat_roughness,
502 sheen: m.sheen,
503 transmission: m.transmission,
504 ior: m.ior,
505 dispersion: m.dispersion,
506 thickness: m.thickness,
507 subsurface: m.subsurface,
508 subsurface_tint: m.subsurface_tint,
509 absorption: m.absorption,
510 thin_film: m.thin_film,
511 thin_film_ior: m.thin_film_ior,
512 thin_film_amount: m.thin_film_amount,
513 emission: m.emission,
514 fresnel_power: m.fresnel_power,
515 normal: m.normal,
516 maps: m.maps,
517 content: m.content,
518 content_layer: m.content_layer,
519 layers: m
520 .layers
521 .into_iter()
522 .filter(|layer| layer.kind != NoiseKind::None)
523 .collect(),
524 ageing: m.ageing,
525 }
526 }
527}
528
529impl Recipe {
530 pub(crate) fn into_material(self) -> Result<Material, String> {
531 if self.layers.len() > LAYER_CAP {
532 return Err(format!(
533 "a material takes at most {LAYER_CAP} noise layers, got {}",
534 self.layers.len()
535 ));
536 }
537 let mut layers = [NoiseLayer::default(); LAYER_CAP];
538 for (slot, layer) in layers.iter_mut().zip(self.layers) {
539 *slot = layer;
540 }
541 Ok(Material {
542 family: self.family,
543 base: self.base,
544 roughness: self.roughness,
545 metalness: self.metalness,
546 specular: self.specular,
547 clearcoat: self.clearcoat,
548 clearcoat_roughness: self.clearcoat_roughness,
549 sheen: self.sheen,
550 transmission: self.transmission,
551 ior: self.ior,
552 dispersion: self.dispersion,
553 thickness: self.thickness,
554 subsurface: self.subsurface,
555 subsurface_tint: self.subsurface_tint,
556 absorption: self.absorption,
557 thin_film: self.thin_film,
558 thin_film_ior: self.thin_film_ior,
559 thin_film_amount: self.thin_film_amount,
560 emission: self.emission,
561 fresnel_power: self.fresnel_power,
562 normal: self.normal,
563 maps: self.maps,
564 content: self.content,
565 content_layer: self.content_layer,
566 layers,
567 ageing: self.ageing,
568 })
569 }
570}
571
572fn pack_id(id: u32) -> f32 {
573 id as f32
574}
575
576fn unpack_id(v: f32) -> u32 {
577 v.round().max(0.0) as u32
578}
579
580#[repr(C, align(16))]
581#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
582pub struct PackedMaterial {
583 pub base_roughness: [f32; 4],
584 pub metal_spec_coat: [f32; 4],
585 pub sheen_trans_ior_disp: [f32; 4],
586 pub thick_sub_film: [f32; 4],
587 pub tint_absorption: [f32; 4],
588 pub emission_film: [f32; 4],
589 pub normal_maps: [f32; 4],
590 pub maps_content: [f32; 4],
591 pub age_a: [f32; 4],
592 pub age_b: [f32; 4],
593 pub layer0: [f32; 4],
594 pub layer1: [f32; 4],
595 pub layer2: [f32; 4],
596 pub layer3: [f32; 4],
597 pub tail: [f32; 4],
598 pub params: [[f32; 4]; PARAM_ROWS],
599}
600
601pub const PARAM_ROWS: usize = LAYER_CAP * GPU_PARAMS / 4;
602
603impl PackedMaterial {
604 pub const SIZE: usize = 240 + PARAM_ROWS * 16;
605 pub const ALIGN: usize = 16;
606
607 pub fn pack(m: &Material) -> Self {
608 let layer = |i: usize| {
609 let layer = m.layers[i];
610 [
611 pack_id(layer.kind.id()),
612 layer.frequency,
613 layer.amplitude,
614 f32::from_bits(layer.seed),
615 ]
616 };
617 Self {
618 base_roughness: [m.base[0], m.base[1], m.base[2], m.roughness],
619 metal_spec_coat: [m.metalness, m.specular, m.clearcoat, m.clearcoat_roughness],
620 sheen_trans_ior_disp: [m.sheen, m.transmission, m.ior, m.dispersion],
621 thick_sub_film: [m.thickness, m.subsurface, m.thin_film, m.thin_film_ior],
622 tint_absorption: [
623 m.subsurface_tint[0],
624 m.subsurface_tint[1],
625 m.subsurface_tint[2],
626 m.absorption,
627 ],
628 emission_film: [
629 m.emission[0],
630 m.emission[1],
631 m.emission[2],
632 m.thin_film_amount,
633 ],
634 normal_maps: [
635 pack_id(m.normal.source.id()),
636 m.normal.strength,
637 m.maps.layer,
638 m.maps.tile,
639 ],
640 maps_content: [
641 m.maps.normal,
642 m.maps.albedo,
643 pack_id(m.content.id()),
644 pack_id(m.family.id()),
645 ],
646 age_a: [
647 m.ageing.fade,
648 m.ageing.yellow,
649 m.ageing.ink,
650 m.ageing.scratch,
651 ],
652 age_b: [
653 m.ageing.edge,
654 m.ageing.dust,
655 m.ageing.patina,
656 m.fresnel_power,
657 ],
658 layer0: layer(0),
659 layer1: layer(1),
660 layer2: layer(2),
661 layer3: layer(3),
662 tail: [f32::from_bits(m.ageing.seed), 0.0, 0.0, 0.0],
663 params: std::array::from_fn(|row| {
664 let params = gpu_params(&m.layers[row / (GPU_PARAMS / 4)]);
665 let at = row % (GPU_PARAMS / 4) * 4;
666 [params[at], params[at + 1], params[at + 2], params[at + 3]]
667 }),
668 }
669 }
670
671 pub fn unpack(self) -> Material {
672 let layer = |row: [f32; 4], index: usize| {
673 let kind = NoiseKind::from_id(unpack_id(row[0]));
674 let params: [f32; PARAMS] =
675 std::array::from_fn(|k| self.params[index * GPU_PARAMS / 4 + k / 4][k % 4]);
676 NoiseLayer {
677 kind,
678 frequency: row[1],
679 amplitude: row[2],
680 seed: row[3].to_bits(),
681 params,
682 }
683 };
684 Material {
685 family: Family::from_id(unpack_id(self.maps_content[3])),
686 base: [
687 self.base_roughness[0],
688 self.base_roughness[1],
689 self.base_roughness[2],
690 ],
691 roughness: self.base_roughness[3],
692 metalness: self.metal_spec_coat[0],
693 specular: self.metal_spec_coat[1],
694 clearcoat: self.metal_spec_coat[2],
695 clearcoat_roughness: self.metal_spec_coat[3],
696 sheen: self.sheen_trans_ior_disp[0],
697 transmission: self.sheen_trans_ior_disp[1],
698 ior: self.sheen_trans_ior_disp[2],
699 dispersion: self.sheen_trans_ior_disp[3],
700 thickness: self.thick_sub_film[0],
701 subsurface: self.thick_sub_film[1],
702 thin_film: self.thick_sub_film[2],
703 thin_film_ior: self.thick_sub_film[3],
704 subsurface_tint: [
705 self.tint_absorption[0],
706 self.tint_absorption[1],
707 self.tint_absorption[2],
708 ],
709 absorption: self.tint_absorption[3],
710 emission: [
711 self.emission_film[0],
712 self.emission_film[1],
713 self.emission_film[2],
714 ],
715 thin_film_amount: self.emission_film[3],
716 fresnel_power: self.age_b[3],
717 normal: Normal {
718 source: NormalSource::from_id(unpack_id(self.normal_maps[0])),
719 strength: self.normal_maps[1],
720 },
721 maps: Maps {
722 layer: self.normal_maps[2],
723 tile: self.normal_maps[3],
724 normal: self.maps_content[0],
725 albedo: self.maps_content[1],
726 },
727 content: Content::from_id(unpack_id(self.maps_content[2])),
728 content_layer: ContentLayer::default(),
729 layers: [
730 layer(self.layer0, 0),
731 layer(self.layer1, 1),
732 layer(self.layer2, 2),
733 layer(self.layer3, 3),
734 ],
735 ageing: Ageing {
736 fade: self.age_a[0],
737 yellow: self.age_a[1],
738 ink: self.age_a[2],
739 scratch: self.age_a[3],
740 edge: self.age_b[0],
741 dust: self.age_b[1],
742 patina: self.age_b[2],
743 seed: self.tail[0].to_bits(),
744 },
745 }
746 }
747}