Skip to main content

kvas_engine/
material_presets.rs

1//! Built-in procedural materials. Every preset is an ordinary node graph (open it in the node editor
2//! and change anything). The editor installs them into `assets/materials/library/` of every project.
3//!
4//! The files are generated: `cargo run -p kvas_engine --example gen_material_library`.
5//!
6//! ```
7//! let all = kvas_engine::material_presets::library();
8//! assert!(all.len() >= 250);
9//! assert!(all.iter().all(|p| kvas_engine::material_graph::to_wgsl(&p.graph).is_ok()));
10//! ```
11
12use crate::material_graph::{GraphMode, MaterialGraph};
13
14/// One shipped material.
15pub struct Preset {
16    /// Folder inside the library (`Stone`, `Metal`...).
17    pub category: &'static str,
18    /// File name without extension.
19    pub name: &'static str,
20    /// The node graph.
21    pub graph: MaterialGraph,
22}
23
24/// Output sockets of the Output node.
25const BASE: usize = 0;
26const ROUGH: usize = 1;
27const METAL: usize = 2;
28const EMIT: usize = 3;
29const ALPHA: usize = 4;
30const NORMAL: usize = 6;
31
32struct B {
33    g: MaterialGraph,
34    out: u32,
35    col: [f32; 8],
36}
37
38impl B {
39    fn new() -> Self {
40        let mut g = MaterialGraph { nodes: Vec::new(), textures: Default::default(), next_id: 1, mode: GraphMode::Material };
41        let out = g.add_node("Output", [1100.0, 120.0]);
42        B { g, out, col: [0.0; 8] }
43    }
44
45    /// Adds a node in a column (0 = leftmost); rows are stacked automatically.
46    fn node(&mut self, kind: &str, column: usize) -> u32 {
47        let y = self.col[column.min(7)];
48        self.col[column.min(7)] += 190.0;
49        self.g.add_node(kind, [40.0 + column as f32 * 250.0, 30.0 + y])
50    }
51
52    fn set(&mut self, id: u32, params: &[f32]) -> u32 {
53        let n = self.g.nodes.iter_mut().find(|n| n.id == id).unwrap();
54        for (i, v) in params.iter().enumerate() {
55            n.params[i] = *v;
56        }
57        id
58    }
59
60    fn value(&mut self, id: u32, input: usize, v: [f32; 3]) {
61        let n = self.g.nodes.iter_mut().find(|n| n.id == id).unwrap();
62        n.inputs[input].value = v;
63        n.inputs[input].override_default = true;
64    }
65
66    fn link(&mut self, from: u32, to: u32, input: usize) {
67        assert!(self.g.connect(from, to, input), "link {from} -> {to}.{input}");
68    }
69
70    fn out(&mut self, from: u32, input: usize) {
71        self.link(from, self.out, input);
72    }
73
74    fn out_value(&mut self, input: usize, v: f32) {
75        let out = self.out;
76        self.value(out, input, [v; 3]);
77    }
78
79    /// Four-stop colour ramp driven by `source`.
80    fn ramp(&mut self, source: u32, stops: [[f32; 3]; 4], pos: [f32; 4], column: usize) -> u32 {
81        let r = self.node("Color ramp", column);
82        self.set(r, &pos);
83        for (i, s) in stops.iter().enumerate() {
84            self.value(r, i + 1, *s);
85        }
86        self.link(source, r, 0);
87        r
88    }
89
90    fn map_range(&mut self, source: u32, lo: f32, hi: f32, column: usize) -> u32 {
91        let m = self.node("Map range", column);
92        self.set(m, &[0.0, 1.0, lo, hi]);
93        self.link(source, m, 0);
94        m
95    }
96
97    fn bump(&mut self, height: u32, strength: f32, column: usize) -> u32 {
98        let b = self.node("Bump from height", column);
99        self.set(b, &[strength]);
100        self.link(height, b, 0);
101        self.out(b, NORMAL);
102        b
103    }
104
105    fn fractal(&mut self, scale: f32, octaves: f32, kind: f32, column: usize) -> u32 {
106        let n = self.node("Fractal noise", column);
107        self.set(n, &[scale, octaves, 2.0, 0.5, kind]);
108        n
109    }
110}
111
112/// A rough natural surface: layered noise coloured by a ramp, with matching roughness and bump.
113#[allow(clippy::too_many_arguments)]
114fn natural(stops: [[f32; 3]; 4], pos: [f32; 4], scale: f32, kind: f32, rough: (f32, f32), metal: f32, bump: f32) -> MaterialGraph {
115    let mut b = B::new();
116    let big = b.fractal(scale, 5.0, kind, 0);
117    let fine = b.fractal(scale * 7.0, 3.0, 0.0, 0);
118    let ramp = b.ramp(big, stops, pos, 1);
119    let detail = b.node("Blend colours", 2);
120    b.set(detail, &[1.0]);
121    b.link(ramp, detail, 0);
122    let grain = b.map_range(fine, 0.8, 1.15, 1);
123    b.link(grain, detail, 1);
124    b.out(detail, BASE);
125    let r = b.map_range(big, rough.0, rough.1, 2);
126    b.out(r, ROUGH);
127    b.out_value(METAL, metal);
128    // Bump from the smooth layer only: derivatives of fine noise look blocky at 2x2 pixels.
129    b.bump(big, bump * 0.5, 3);
130    b.g
131}
132
133fn metal_brushed(color: [f32; 3], dark: [f32; 3], rough: f32, scratch: f32) -> MaterialGraph {
134    let mut b = B::new();
135    let s = b.node("Scratches", 0);
136    b.set(s, &[scratch, 0.0]);
137    let n = b.fractal(3.0, 4.0, 0.0, 0);
138    let mix = b.node("Mix", 1);
139    b.value(mix, 0, color);
140    b.value(mix, 1, dark);
141    let soft = b.map_range(s, 0.0, 0.45, 1);
142    b.link(soft, mix, 2);
143    b.out(mix, BASE);
144    let r = b.map_range(n, rough * 0.7, rough * 1.3, 1);
145    b.out(r, ROUGH);
146    b.out_value(METAL, 1.0);
147    b.g
148}
149
150fn emissive(name_ramp: [[f32; 3]; 4], pos: [f32; 4], scale: f32, kind: f32, intensity: f32, base: [f32; 3], rough: f32) -> MaterialGraph {
151    let mut b = B::new();
152    let n = b.fractal(scale, 5.0, kind, 0);
153    let ramp = b.ramp(n, name_ramp, pos, 1);
154    let glow = b.node("Multiply", 2);
155    b.link(ramp, glow, 0);
156    b.value(glow, 1, [intensity; 3]);
157    b.out(glow, EMIT);
158    let dark = b.node("Mix", 2);
159    b.value(dark, 0, base);
160    b.value(dark, 1, [0.01; 3]);
161    b.link(n, dark, 2);
162    b.out(dark, BASE);
163    b.out_value(ROUGH, rough);
164    b.bump(n, 1.5, 3);
165    b.g
166}
167
168fn tiles(color_a: [f32; 3], color_b: [f32; 3], count: f32, gap: f32, rough: f32, metal: f32) -> MaterialGraph {
169    let mut b = B::new();
170    let grid = b.node("Grid lines", 0);
171    b.set(grid, &[count, gap]);
172    let checker = b.node("Checker", 0);
173    b.set(checker, &[count]);
174    let mix = b.node("Mix", 1);
175    b.value(mix, 0, color_a);
176    b.value(mix, 1, color_b);
177    b.link(checker, mix, 2);
178    let grout = b.node("Mix", 2);
179    b.link(mix, grout, 0);
180    b.value(grout, 1, [0.08, 0.08, 0.08]);
181    b.link(grid, grout, 2);
182    b.out(grout, BASE);
183    b.out_value(ROUGH, rough);
184    b.out_value(METAL, metal);
185    let inv = b.node("One minus", 1);
186    b.link(grid, inv, 0);
187    b.bump(inv, 2.0, 2);
188    b.g
189}
190
191fn wood(light: [f32; 3], dark: [f32; 3], planks: bool) -> MaterialGraph {
192    let mut b = B::new();
193    let rings = b.node("Wood rings", 0);
194    b.set(rings, &[4.0, 0.35]);
195    let fine = b.fractal(40.0, 3.0, 0.0, 0);
196    let grain = b.node("Blend colours", 1);
197    b.set(grain, &[1.0]);
198    b.link(rings, grain, 0);
199    b.link(fine, grain, 1);
200    let ramp = b.ramp(grain, [dark, [dark[0] * 1.5, dark[1] * 1.4, dark[2] * 1.3], light, [light[0] * 1.1, light[1] * 1.1, light[2] * 1.05]], [0.0, 0.35, 0.7, 1.0], 2);
201    if planks {
202        let pl = b.node("Planks", 0);
203        b.set(pl, &[6.0, 3.0, 0.02]);
204        let shade = b.node("Multiply", 3);
205        b.link(ramp, shade, 0);
206        b.link(pl, shade, 1);
207        b.out(shade, BASE);
208        b.bump(pl, 1.0, 3);
209    } else {
210        b.out(ramp, BASE);
211        b.bump(fine, 0.6, 3);
212    }
213    b.out_value(ROUGH, 0.55);
214    b.g
215}
216
217/// Marble built from general nodes (see [`crate::material_presets_v4::marble`]).
218fn marble(base: [f32; 3], vein: [f32; 3], warp: f32) -> MaterialGraph {
219    crate::material_presets_v4::marble(base, vein, warp * 0.75, 1.6)
220}
221
222fn bricks(brick: [f32; 3], mortar: [f32; 3]) -> MaterialGraph {
223    let mut b = B::new();
224    let br = b.node("Bricks", 0);
225    b.set(br, &[6.0, 0.05]);
226    let n = b.fractal(12.0, 4.0, 0.0, 0);
227    let cells = b.node("Voronoi cells", 0);
228    b.set(cells, &[8.0, 1.0, 2.0]);
229    let tone = b.ramp(cells, [[brick[0] * 0.6, brick[1] * 0.6, brick[2] * 0.6], brick, [brick[0] * 1.25, brick[1] * 1.15, brick[2] * 1.1], brick], [0.0, 0.4, 0.8, 1.0], 1);
230    let mix = b.node("Mix", 2);
231    b.value(mix, 0, mortar);
232    b.link(tone, mix, 1);
233    b.link(br, mix, 2);
234    let dirt = b.node("Blend colours", 3);
235    b.set(dirt, &[1.0]);
236    b.link(mix, dirt, 0);
237    let grime = b.map_range(n, 0.7, 1.1, 1);
238    b.link(grime, dirt, 1);
239    b.out(dirt, BASE);
240    b.out_value(ROUGH, 0.85);
241    b.bump(br, 3.0, 2);
242    b.g
243}
244
245fn weave(a: [f32; 3], bcol: [f32; 3], threads: f32, metal: f32, rough: f32) -> MaterialGraph {
246    let mut b = B::new();
247    let w = b.node("Weave", 0);
248    b.set(w, &[threads]);
249    let mix = b.node("Mix", 1);
250    b.value(mix, 0, a);
251    b.value(mix, 1, bcol);
252    b.link(w, mix, 2);
253    let shade = b.node("Multiply", 2);
254    b.link(mix, shade, 0);
255    b.link(w, shade, 1);
256    b.out(shade, BASE);
257    b.out_value(ROUGH, rough);
258    b.out_value(METAL, metal);
259    b.bump(w, 2.5, 1);
260    b.g
261}
262
263fn hex_metal(base: [f32; 3], edge: [f32; 3], scale: f32) -> MaterialGraph {
264    let mut b = B::new();
265    let h = b.node("Hexagons", 0);
266    b.set(h, &[scale, 0.08]);
267    let mix = b.node("Mix", 1);
268    b.value(mix, 0, edge);
269    b.value(mix, 1, base);
270    b.link(h, mix, 2);
271    b.out(mix, BASE);
272    b.out_value(ROUGH, 0.35);
273    b.out_value(METAL, 1.0);
274    b.bump(h, 2.0, 1);
275    b.g
276}
277
278fn cracked(base: [f32; 3], crack: [f32; 3], scale: f32, rough: f32) -> MaterialGraph {
279    let mut b = B::new();
280    let c = b.node("Cracks", 0);
281    b.set(c, &[scale, 0.07]);
282    let n = b.fractal(6.0, 5.0, 0.0, 0);
283    let mix = b.node("Mix", 1);
284    b.value(mix, 0, base);
285    b.value(mix, 1, crack);
286    b.link(c, mix, 2);
287    let shade = b.node("Blend colours", 2);
288    b.set(shade, &[1.0]);
289    b.link(mix, shade, 0);
290    let m = b.map_range(n, 0.75, 1.1, 1);
291    b.link(m, shade, 1);
292    b.out(shade, BASE);
293    b.out_value(ROUGH, rough);
294    let inv = b.node("One minus", 1);
295    b.link(c, inv, 0);
296    b.bump(inv, 2.5, 2);
297    b.g
298}
299
300fn neon_grid(line: [f32; 3], intensity: f32) -> MaterialGraph {
301    let mut b = B::new();
302    let g = b.node("Grid lines", 0);
303    b.set(g, &[10.0, 0.03]);
304    let glow = b.node("Multiply", 1);
305    b.value(glow, 0, line);
306    b.link(g, glow, 1);
307    let boost = b.node("Multiply", 2);
308    b.link(glow, boost, 0);
309    b.value(boost, 1, [intensity; 3]);
310    let pulse = b.node("Pulse", 0);
311    b.set(pulse, &[0.5]);
312    let mixed = b.node("Mix", 3);
313    b.link(boost, mixed, 1);
314    b.value(mixed, 0, [0.0; 3]);
315    b.link(pulse, mixed, 2);
316    b.out(boost, EMIT);
317    b.value(b.out, BASE, [0.02, 0.02, 0.03]);
318    b.out_value(ROUGH, 0.3);
319    b.g
320}
321
322fn glass(tint: [f32; 3], alpha: f32, rough: f32) -> MaterialGraph {
323    let mut b = B::new();
324    let f = b.node("Fresnel", 0);
325    b.value(f, 0, [3.0; 3]);
326    let a = b.map_range(f, alpha, 1.0, 1);
327    b.out(a, ALPHA);
328    b.value(b.out, BASE, tint);
329    b.out_value(ROUGH, rough);
330    b.out_value(METAL, 0.0);
331    b.g
332}
333
334fn plain(color: [f32; 3], rough: f32, metal: f32) -> MaterialGraph {
335    let mut b = B::new();
336    b.value(b.out, BASE, color);
337    b.out_value(ROUGH, rough);
338    b.out_value(METAL, metal);
339    b.g
340}
341
342/// Flat, hand-painted look: a noise is posterised into a few bands, then coloured by a ramp. No bump, matte.
343fn stylized(stops: [[f32; 3]; 4], scale: f32, steps: f32, kind: f32) -> MaterialGraph {
344    let mut b = B::new();
345    let n = b.fractal(scale, 4.0, kind, 0);
346    let post = b.node("Posterize", 1);
347    b.set(post, &[steps]);
348    b.link(n, post, 0);
349    let ramp = b.ramp(post, stops, [0.0, 0.34, 0.67, 1.0], 2);
350    b.out(ramp, BASE);
351    b.out_value(ROUGH, 0.85);
352    b.g
353}
354
355/// A pattern node (`kind`, parameters) turned into colours by a two-colour mix.
356fn pattern(kind: &str, params: &[f32], a: [f32; 3], bcol: [f32; 3], rough: f32) -> MaterialGraph {
357    let mut b = B::new();
358    let pat = b.node(kind, 0);
359    b.set(pat, params);
360    let mix = b.node("Mix", 1);
361    b.value(mix, 0, a);
362    b.value(mix, 1, bcol);
363    b.link(pat, mix, 2);
364    b.out(mix, BASE);
365    b.out_value(ROUGH, rough);
366    b.g
367}
368
369/// A gradient (`gradient` node: Linear / Radial / Angular) coloured by a ramp.
370fn gradient(kind: &str, stops: [[f32; 3]; 4], emission: f32) -> MaterialGraph {
371    let mut b = B::new();
372    let g = b.node(kind, 0);
373    let ramp = b.ramp(g, stops, [0.0, 0.34, 0.67, 1.0], 1);
374    if emission > 0.0 {
375        let glow = b.node("Multiply", 2);
376        b.link(ramp, glow, 0);
377        b.value(glow, 1, [emission; 3]);
378        b.out(glow, EMIT);
379        b.value(b.out, BASE, [0.02; 3]);
380    } else {
381        b.out(ramp, BASE);
382    }
383    b.out_value(ROUGH, 0.7);
384    b.g
385}
386
387/// Rainbow colours from a warped noise (tie-dye) or from the viewing angle (holographic foil).
388fn rainbow(from_view: bool, scale: f32) -> MaterialGraph {
389    let mut b = B::new();
390    let src = if from_view {
391        let f = b.node("Fresnel", 0);
392        b.value(f, 0, [1.5; 3]);
393        f
394    } else {
395        let w = b.node("Warped noise", 0);
396        b.set(w, &[scale, 2.5]);
397        w
398    };
399    let hsv = b.node("HSV to RGB", 1);
400    b.link(src, hsv, 0);
401    b.value(hsv, 1, [0.75; 3]);
402    b.value(hsv, 2, [1.0; 3]);
403    b.out(hsv, BASE);
404    if from_view {
405        let glow = b.node("Multiply", 2);
406        b.link(hsv, glow, 0);
407        b.value(glow, 1, [0.6; 3]);
408        b.out(glow, EMIT);
409        b.out_value(METAL, 0.8);
410        b.out_value(ROUGH, 0.2);
411    } else {
412        b.out_value(ROUGH, 0.8);
413    }
414    b.g
415}
416
417/// Starfield: dark gradient clouds with bright sparkle points as emission.
418fn galaxy(a: [f32; 3], bcol: [f32; 3]) -> MaterialGraph {
419    let mut b = B::new();
420    let n = b.node("Clouds", 0);
421    b.set(n, &[2.0, 0.3, 0.6]);
422    let ramp = b.ramp(n, [[0.01, 0.01, 0.05], a, bcol, [1.0, 0.9, 1.0]], [0.0, 0.4, 0.75, 1.0], 1);
423    let stars = b.node("Sparkle", 0);
424    b.set(stars, &[90.0, 0.04]);
425    let sum = b.node("Add", 2);
426    b.link(ramp, sum, 0);
427    b.link(stars, sum, 1);
428    b.out(sum, EMIT);
429    b.value(b.out, BASE, [0.0; 3]);
430    b.g
431}
432
433/// Animated water. `stylized` posterises the waves into flat bands with foam instead of Fresnel reflections.
434/// The speed is the scroll speed of the wave layers (the "Scroll coords" nodes), so it is tweakable in the graph.
435fn water(deep: [f32; 3], shallow: [f32; 3], speed: f32, scale: f32, stylized: bool, alpha: f32) -> MaterialGraph {
436    let mut b = B::new();
437    let uv = b.node("UV", 0);
438    let s1 = b.node("Scroll coords", 1);
439    b.set(s1, &[speed, speed * 0.4]);
440    b.link(uv, s1, 0);
441    let s2 = b.node("Scroll coords", 1);
442    b.set(s2, &[-speed * 0.7, speed * 0.9]);
443    b.link(uv, s2, 0);
444    let n1 = b.fractal(scale, 4.0, 0.0, 2);
445    let n2 = b.fractal(scale * 2.3, 3.0, 2.0, 2);
446    b.link(s1, n1, 0);
447    b.link(s2, n2, 0);
448    let waves = b.node("Average", 3);
449    b.link(n1, waves, 0);
450    b.link(n2, waves, 1);
451    let source = if stylized {
452        let post = b.node("Posterize", 3);
453        b.set(post, &[4.0]);
454        b.link(waves, post, 0);
455        post
456    } else {
457        waves
458    };
459    let mid = [(deep[0] + shallow[0]) * 0.5, (deep[1] + shallow[1]) * 0.5, (deep[2] + shallow[2]) * 0.5];
460    let ramp = b.ramp(source, [deep, mid, shallow, [shallow[0] * 1.2 + 0.1, shallow[1] * 1.2 + 0.1, shallow[2] * 1.2 + 0.1]], [0.15, 0.45, 0.7, 0.95], 4);
461    if stylized {
462        // White foam where the wave crests.
463        let foam = b.node("Greater than", 4);
464        b.link(waves, foam, 0);
465        b.value(foam, 1, [0.78; 3]);
466        let mixed = b.node("Mix", 5);
467        b.link(ramp, mixed, 0);
468        b.value(mixed, 1, [1.0; 3]);
469        b.link(foam, mixed, 2);
470        b.out(mixed, BASE);
471        b.out_value(ROUGH, 0.35);
472    } else {
473        b.out(ramp, BASE);
474        b.out_value(ROUGH, 0.04);
475        b.bump(waves, 2.5, 5);
476        // Faint moving caustics glow.
477        let caustics = b.node("Caustics", 4);
478        let glow = b.node("Multiply", 5);
479        b.link(caustics, glow, 0);
480        b.value(glow, 1, [0.12; 3]);
481        b.out(glow, EMIT);
482    }
483    let fres = b.node("Fresnel", 4);
484    b.value(fres, 0, [3.0; 3]);
485    let a = b.map_range(fres, alpha, 1.0, 5);
486    b.out(a, ALPHA);
487    b.g
488}
489
490/// Deep layered materials: several noise layers sit at different depths and shift with the viewing angle
491/// (parallax), like looking into space, a nebula or a crystal. Pure emission, so it glows in the dark.
492fn deep_layers(pal: [[f32; 3]; 3], speed: f32, stars: f32, scales: [f32; 3]) -> MaterialGraph {
493    let mut b = B::new();
494    let uv = b.node("UV", 0);
495    let view = b.node("View direction", 0);
496    let mut sum: Option<u32> = None;
497    for (i, (depth, scale)) in [(0.0f32, scales[0]), (0.18, scales[1]), (0.45, scales[2])].into_iter().enumerate() {
498        let off = b.node("Multiply", 1);
499        b.link(view, off, 0);
500        b.value(off, 1, [depth; 3]);
501        let shifted = b.node("Add", 1);
502        b.link(uv, shifted, 0);
503        b.link(off, shifted, 1);
504        let scroll = b.node("Scroll coords", 2);
505        b.set(scroll, &[speed * (i as f32 + 1.0) * 0.02, speed * 0.01]);
506        b.link(shifted, scroll, 0);
507        let noise = b.fractal(scale, 4.0, if i == 1 { 1.0 } else { 0.0 }, 3);
508        b.link(scroll, noise, 0);
509        let ramp = b.ramp(noise, [[0.0; 3], tint(pal[i % 3], [0.5; 3]), tint(pal[(i + 1) % 3], [0.8; 3]), [0.75, 0.72, 0.8]], [0.25, 0.5, 0.75, 1.0], 4);
510        let mask = b.node("Smoothstep", 4);
511        b.set(mask, &[0.3, 0.8]);
512        b.link(noise, mask, 0);
513        let layer = b.node("Multiply", 5);
514        b.link(ramp, layer, 0);
515        b.link(mask, layer, 1);
516        sum = Some(match sum {
517            None => layer,
518            Some(prev) => {
519                let add = b.node("Add", 6);
520                b.link(prev, add, 0);
521                b.link(layer, add, 1);
522                add
523            }
524        });
525    }
526    let star_shift = b.node("Multiply", 1);
527    b.link(view, star_shift, 0);
528    b.value(star_shift, 1, [0.6; 3]);
529    let star_uv = b.node("Add", 2);
530    b.link(uv, star_uv, 0);
531    b.link(star_shift, star_uv, 1);
532    let sparkle = b.node("Sparkle", 3);
533    b.set(sparkle, &[70.0, stars]);
534    b.link(star_uv, sparkle, 0);
535    let boost = b.node("Multiply", 5);
536    b.link(sparkle, boost, 0);
537    b.value(boost, 1, [2.0; 3]);
538    let total = b.node("Add", 7);
539    b.link(sum.unwrap(), total, 0);
540    b.link(boost, total, 1);
541    b.out(total, EMIT);
542    b.value(b.out, BASE, [0.0; 3]);
543    b.out_value(ROUGH, 0.5);
544    b.g
545}
546
547fn leak(s: String) -> &'static str {
548    Box::leak(s.into_boxed_str())
549}
550
551fn tint(c: [f32; 3], t: [f32; 3]) -> [f32; 3] {
552    [c[0] * t[0], c[1] * t[1], c[2] * t[2]]
553}
554
555fn tint4(c: [[f32; 3]; 4], t: [f32; 3]) -> [[f32; 3]; 4] {
556    [tint(c[0], t), tint(c[1], t), tint(c[2], t), tint(c[3], t)]
557}
558
559/// Colour variants of the base recipes plus water, deep-space and stylized families.
560fn extra(v: &mut Vec<Preset>) {
561    const TINTS: [(&str, [f32; 3]); 6] = [
562        ("Red", [1.3, 0.7, 0.65]),
563        ("Blue", [0.7, 0.85, 1.3]),
564        ("Green", [0.75, 1.15, 0.8]),
565        ("Brown", [1.15, 0.9, 0.7]),
566        ("Pink", [1.25, 0.85, 1.0]),
567        ("Dark", [0.45, 0.45, 0.48]),
568    ];
569    // ---- stone and ground variants
570    let stones: [(&str, [[f32; 3]; 4], f32, f32, (f32, f32), f32); 5] = [
571        ("Granite", [[0.18, 0.18, 0.19], [0.42, 0.41, 0.42], [0.62, 0.6, 0.6], [0.8, 0.78, 0.78]], 6.0, 0.0, (0.4, 0.7), 1.0),
572        ("Rock", [[0.12, 0.11, 0.1], [0.28, 0.25, 0.22], [0.45, 0.4, 0.35], [0.62, 0.57, 0.5]], 2.5, 1.0, (0.75, 1.0), 4.0),
573        ("Sandstone", [[0.5, 0.36, 0.2], [0.68, 0.5, 0.3], [0.8, 0.62, 0.4], [0.88, 0.72, 0.5]], 3.5, 2.0, (0.8, 1.0), 2.0),
574        ("Slate", [[0.06, 0.07, 0.09], [0.14, 0.16, 0.2], [0.22, 0.25, 0.3], [0.32, 0.35, 0.4]], 4.0, 1.0, (0.45, 0.8), 1.5),
575        ("Concrete", [[0.3, 0.3, 0.3], [0.42, 0.42, 0.41], [0.52, 0.52, 0.5], [0.6, 0.6, 0.58]], 5.0, 0.0, (0.85, 1.0), 0.8),
576    ];
577    for (name, stops, scale, kind, rough, bump) in stones {
578        for (tname, t) in TINTS {
579            v.push(p("Stone Variants", leak(format!("{name} {tname}")), natural(tint4(stops, t), [0.0, 0.35, 0.65, 1.0], scale, kind, rough, 0.0, bump)));
580        }
581    }
582    // ---- metals
583    let metals = [("Silver", [0.8, 0.8, 0.82]), ("Steel Blue", [0.5, 0.6, 0.75]), ("Bronze", [0.8, 0.5, 0.2]), ("Brass", [0.85, 0.7, 0.3]), ("Rose Gold", [0.95, 0.62, 0.55]), ("Gunmetal", [0.25, 0.27, 0.3]),
584        ("Black Steel", [0.08, 0.08, 0.09]), ("Platinum", [0.85, 0.84, 0.82]), ("Cobalt", [0.2, 0.35, 0.65]), ("Emerald Metal", [0.1, 0.55, 0.35]), ("Ruby Metal", [0.65, 0.07, 0.1]), ("Violet Metal", [0.45, 0.25, 0.7])];
585    for (name, c) in metals {
586        v.push(p("Metal Variants", leak(format!("{name} Brushed")), metal_brushed(c, tint(c, [0.65; 3]), 0.32, 160.0)));
587        v.push(p("Metal Variants", leak(format!("{name} Polished")), plain(c, 0.08, 1.0)));
588    }
589    // ---- wood species
590    let woods = [("Birch", [0.85, 0.75, 0.55], [0.6, 0.48, 0.3]), ("Cherry", [0.55, 0.25, 0.15], [0.35, 0.14, 0.08]), ("Ebony", [0.12, 0.09, 0.07], [0.04, 0.03, 0.025]), ("Maple", [0.8, 0.62, 0.4], [0.58, 0.42, 0.25]),
591        ("Mahogany", [0.45, 0.2, 0.12], [0.25, 0.1, 0.06]), ("Ash", [0.75, 0.68, 0.55], [0.5, 0.42, 0.32]), ("Teak", [0.6, 0.42, 0.2], [0.38, 0.24, 0.1]), ("Bamboo", [0.8, 0.72, 0.4], [0.55, 0.5, 0.25])];
592    for (name, light, dark) in woods {
593        v.push(p("Wood Variants", leak(format!("{name}")), wood(light, dark, false)));
594        v.push(p("Wood Variants", leak(format!("{name} Planks")), wood(light, dark, true)));
595    }
596    // ---- fabrics, tiles, plastics
597    let colours = [("Red", [0.7, 0.05, 0.05]), ("Orange", [0.9, 0.4, 0.05]), ("Yellow", [0.95, 0.8, 0.1]), ("Green", [0.1, 0.55, 0.15]), ("Teal", [0.05, 0.5, 0.5]), ("Blue", [0.08, 0.2, 0.75]),
598        ("Purple", [0.4, 0.12, 0.6]), ("Pink", [0.95, 0.4, 0.6]), ("White", [0.9, 0.9, 0.88]), ("Grey", [0.35, 0.35, 0.37]), ("Black", [0.03, 0.03, 0.035]), ("Brown", [0.3, 0.17, 0.09])];
599    for (name, c) in colours {
600        v.push(p("Fabric Variants", leak(format!("{name} Cloth")), weave(c, tint(c, [0.8; 3]), 44.0, 0.0, 0.9)));
601        v.push(p("Tile Variants", leak(format!("{name} Tiles")), tiles(c, tint(c, [0.7; 3]), 10.0, 0.04, 0.25, 0.0)));
602        v.push(p("Plastic", leak(format!("{name} Gloss")), plain(c, 0.18, 0.0)));
603        v.push(p("Plastic", leak(format!("{name} Matte")), plain(c, 0.75, 0.0)));
604    }
605    for (name, c) in [("Red", [0.7, 0.2, 0.15]), ("Brown", [0.5, 0.3, 0.2]), ("Grey", [0.5, 0.5, 0.5]), ("Yellow", [0.75, 0.6, 0.3]), ("Dark", [0.25, 0.2, 0.2]), ("White", [0.8, 0.78, 0.74])] {
606        v.push(p("Building Variants", leak(format!("{name} Bricks")), bricks(c, [0.55, 0.53, 0.5])));
607    }
608    for (name, tintc, alpha) in [("Blue", [0.55, 0.75, 1.0], 0.2), ("Green", [0.4, 0.9, 0.55], 0.25), ("Amber", [1.0, 0.7, 0.25], 0.3), ("Red", [1.0, 0.3, 0.3], 0.3), ("Smoke", [0.3, 0.3, 0.32], 0.5), ("Frosted", [0.9, 0.95, 1.0], 0.6), ("Purple", [0.6, 0.3, 0.9], 0.3), ("Pink", [1.0, 0.6, 0.8], 0.3)] {
609        v.push(p("Glass", leak(format!("{name} Glass")), glass(tintc, alpha, if name == "Frosted" { 0.5 } else { 0.04 })));
610    }
611    // ---- water, realistic and stylized
612    let waters: [(&str, [f32; 3], [f32; 3], f32, f32, f32); 10] = [
613        ("Ocean", [0.0, 0.1, 0.28], [0.1, 0.5, 0.65], 0.03, 2.0, 0.55), ("Lake", [0.02, 0.16, 0.22], [0.15, 0.55, 0.55], 0.015, 2.5, 0.5), ("Pool", [0.1, 0.55, 0.8], [0.5, 0.95, 1.0], 0.02, 4.0, 0.25),
614        ("Tropical", [0.0, 0.45, 0.55], [0.3, 1.0, 0.85], 0.03, 3.0, 0.35), ("Swamp", [0.05, 0.12, 0.04], [0.25, 0.4, 0.15], 0.008, 3.0, 0.85), ("River", [0.05, 0.2, 0.25], [0.35, 0.6, 0.6], 0.06, 3.5, 0.5),
615        ("Toxic", [0.1, 0.3, 0.0], [0.6, 1.0, 0.1], 0.025, 2.5, 0.7), ("Ink", [0.0, 0.0, 0.04], [0.12, 0.12, 0.3], 0.02, 2.0, 0.9), ("Milk", [0.85, 0.85, 0.8], [1.0, 1.0, 0.95], 0.01, 2.0, 0.9), ("Lava River", [0.35, 0.03, 0.0], [1.0, 0.55, 0.05], 0.02, 2.0, 1.0),
616    ];
617    for (name, deep, shallow, speed, scale, alpha) in waters {
618        v.push(p("Water", leak(format!("{name} Realistic")), water(deep, shallow, speed, scale, false, alpha)));
619        v.push(p("Water", leak(format!("{name} Stylized")), water(deep, shallow, speed * 1.5, scale * 0.7, true, alpha.max(0.7))));
620    }
621    // ---- deep layered cosmic materials
622    let cosmos: [(&str, [[f32; 3]; 3], f32, f32); 16] = [
623        ("Galaxy Purple", [[0.3, 0.1, 0.7], [0.8, 0.3, 0.8], [0.2, 0.4, 1.0]], 1.0, 0.02), ("Nebula Teal", [[0.05, 0.4, 0.6], [0.2, 0.8, 0.7], [0.6, 0.3, 0.9]], 1.0, 0.025), ("Deep Space", [[0.02, 0.03, 0.15], [0.1, 0.1, 0.4], [0.3, 0.2, 0.6]], 0.5, 0.04),
624        ("Crimson Nebula", [[0.5, 0.05, 0.1], [1.0, 0.3, 0.2], [1.0, 0.7, 0.4]], 1.0, 0.02), ("Aurora", [[0.1, 0.8, 0.4], [0.2, 0.5, 0.9], [0.6, 0.2, 0.8]], 1.5, 0.015), ("Wormhole", [[0.0, 0.2, 0.6], [0.5, 0.8, 1.0], [1.0, 1.0, 1.0]], 2.0, 0.03),
625        ("Energy Core", [[1.0, 0.4, 0.0], [1.0, 0.8, 0.2], [1.0, 1.0, 0.8]], 3.0, 0.0), ("Crystal Depth", [[0.2, 0.7, 0.9], [0.5, 0.9, 1.0], [0.9, 0.5, 1.0]], 0.6, 0.01), ("Abyss", [[0.0, 0.1, 0.2], [0.0, 0.3, 0.4], [0.2, 0.6, 0.6]], 0.5, 0.0),
626        ("Magic Portal", [[0.6, 0.1, 0.9], [0.2, 0.9, 0.9], [1.0, 0.9, 0.3]], 2.5, 0.02), ("Cosmic Dust", [[0.4, 0.3, 0.2], [0.8, 0.6, 0.4], [1.0, 0.9, 0.7]], 1.0, 0.03), ("Pulsar", [[0.1, 0.1, 0.6], [0.3, 0.6, 1.0], [1.0, 1.0, 1.0]], 3.0, 0.04),
627        ("Stardust", [[0.8, 0.7, 0.3], [1.0, 0.9, 0.6], [1.0, 1.0, 1.0]], 0.8, 0.06), ("Supernova", [[1.0, 0.2, 0.0], [1.0, 0.6, 0.1], [0.9, 0.9, 1.0]], 2.0, 0.03), ("Eclipse", [[0.02, 0.0, 0.05], [0.4, 0.1, 0.0], [1.0, 0.6, 0.2]], 0.6, 0.01), ("Quasar", [[0.4, 0.2, 1.0], [0.1, 0.9, 1.0], [1.0, 1.0, 1.0]], 2.5, 0.03),
628    ];
629    for (name, pal, speed, stars) in cosmos {
630        v.push(p("Deep Space", name, deep_layers(pal, speed, stars + 0.02, [2.0, 4.0, 8.0])));
631    }
632    // ---- more stylized: palettes x recipes
633    let palettes: [(&str, [[f32; 3]; 4]); 8] = [
634        ("Meadow", [[0.1, 0.4, 0.15], [0.25, 0.65, 0.2], [0.5, 0.85, 0.3], [0.8, 0.95, 0.5]]), ("Candy", [[0.9, 0.3, 0.6], [1.0, 0.55, 0.75], [1.0, 0.8, 0.85], [1.0, 0.95, 0.95]]),
635        ("Ocean", [[0.0, 0.2, 0.5], [0.0, 0.45, 0.75], [0.2, 0.7, 0.9], [0.8, 0.95, 1.0]]), ("Autumn", [[0.4, 0.1, 0.05], [0.8, 0.3, 0.1], [1.0, 0.6, 0.15], [1.0, 0.85, 0.4]]),
636        ("Frost", [[0.3, 0.45, 0.7], [0.55, 0.75, 0.95], [0.8, 0.92, 1.0], [1.0, 1.0, 1.0]]), ("Shadow", [[0.03, 0.02, 0.08], [0.12, 0.08, 0.25], [0.3, 0.2, 0.5], [0.55, 0.4, 0.8]]),
637        ("Mint", [[0.2, 0.6, 0.5], [0.4, 0.85, 0.7], [0.7, 1.0, 0.85], [0.95, 1.0, 0.95]]), ("Sunrise", [[0.5, 0.15, 0.4], [0.9, 0.35, 0.35], [1.0, 0.65, 0.3], [1.0, 0.92, 0.55]]),
638    ];
639    for (name, stops) in palettes {
640        v.push(p("Stylized Painted", leak(format!("Painted {name}")), stylized(stops, 3.0, 4.0, 0.0)));
641        v.push(p("Stylized Painted", leak(format!("Blobs {name}")), stylized(stops, 1.8, 3.0, 1.0)));
642        v.push(p("Stylized Painted", leak(format!("Clouds {name}")), stylized(stops, 1.2, 5.0, 2.0)));
643        v.push(p("Stylized Painted", leak(format!("Speckle {name}")), stylized(stops, 9.0, 3.0, 0.0)));
644        v.push(p("Stylized Painted", leak(format!("Gradient {name}")), gradient("Linear gradient", stops, 0.0)));
645        v.push(p("Stylized Painted", leak(format!("Radial {name}")), gradient("Radial gradient", stops, 0.0)));
646        v.push(p("Stylized Painted", leak(format!("Stripes {name}")), pattern("Stripes", &[8.0, 0.85], stops[0], stops[2], 0.6)));
647        v.push(p("Stylized Painted", leak(format!("Dots {name}")), pattern("Dots", &[10.0, 0.3], stops[3], stops[1], 0.6)));
648    }
649    // ---- glowing neon grids and plasmas in many colours
650    for (name, c) in [("Red", [1.0, 0.1, 0.1]), ("Orange", [1.0, 0.5, 0.0]), ("Yellow", [1.0, 0.9, 0.1]), ("Green", [0.1, 1.0, 0.3]), ("Blue", [0.15, 0.4, 1.0]), ("Violet", [0.55, 0.2, 1.0]), ("White", [1.0, 1.0, 1.0])] {
651        v.push(p("Emissive", leak(format!("Neon Grid {name}")), neon_grid(c, 6.0)));
652        v.push(p("Emissive", leak(format!("Plasma {name}")), emissive([[c[0] * 0.05, c[1] * 0.05, c[2] * 0.05], tint(c, [0.4; 3]), c, [1.0, 1.0, 1.0]], [0.1, 0.4, 0.7, 1.0], 4.0, 2.0, 3.0, tint(c, [0.05; 3]), 0.3)));
653    }
654}
655
656fn p(category: &'static str, name: &'static str, graph: MaterialGraph) -> Preset {
657    Preset { category, name, graph }
658}
659
660/// Every shipped material.
661pub fn library() -> Vec<Preset> {
662    let sand = [[0.55, 0.45, 0.28], [0.72, 0.6, 0.38], [0.82, 0.72, 0.5], [0.9, 0.82, 0.62]];
663    let mut v = vec![
664        // ---- stone and ground
665        p("Stone", "Granite", natural([[0.18, 0.18, 0.19], [0.42, 0.41, 0.42], [0.62, 0.6, 0.6], [0.8, 0.78, 0.78]], [0.0, 0.35, 0.65, 1.0], 6.0, 0.0, (0.4, 0.7), 0.0, 1.0)),
666        p("Stone", "Rock", natural([[0.12, 0.11, 0.1], [0.28, 0.25, 0.22], [0.45, 0.4, 0.35], [0.62, 0.57, 0.5]], [0.0, 0.3, 0.65, 1.0], 2.5, 1.0, (0.75, 1.0), 0.0, 4.0)),
667        p("Stone", "Sandstone", natural([[0.5, 0.36, 0.2], [0.68, 0.5, 0.3], [0.8, 0.62, 0.4], [0.88, 0.72, 0.5]], [0.0, 0.3, 0.7, 1.0], 3.5, 2.0, (0.8, 1.0), 0.0, 2.0)),
668        p("Stone", "Slate", natural([[0.06, 0.07, 0.09], [0.14, 0.16, 0.2], [0.22, 0.25, 0.3], [0.32, 0.35, 0.4]], [0.0, 0.4, 0.7, 1.0], 4.0, 1.0, (0.45, 0.8), 0.0, 1.5)),
669        p("Stone", "Concrete", natural([[0.3, 0.3, 0.3], [0.42, 0.42, 0.41], [0.52, 0.52, 0.5], [0.6, 0.6, 0.58]], [0.0, 0.4, 0.7, 1.0], 5.0, 0.0, (0.85, 1.0), 0.0, 0.8)),
670        p("Stone", "Asphalt", natural([[0.04, 0.04, 0.045], [0.08, 0.08, 0.09], [0.14, 0.14, 0.15], [0.22, 0.22, 0.23]], [0.0, 0.4, 0.75, 1.0], 12.0, 0.0, (0.8, 1.0), 0.0, 1.5)),
671        p("Stone", "Cobblestone", cracked([0.32, 0.3, 0.28], [0.05, 0.05, 0.05], 5.0, 0.9)),
672        p("Stone", "Marble White", marble([0.92, 0.92, 0.9], [0.35, 0.37, 0.42], 4.0)),
673        p("Stone", "Marble Black", marble([0.04, 0.04, 0.05], [0.8, 0.75, 0.55], 5.0)),
674        p("Stone", "Marble Green", marble([0.08, 0.3, 0.2], [0.85, 0.92, 0.85], 3.0)),
675        p("Ground", "Dirt", natural([[0.1, 0.07, 0.04], [0.2, 0.14, 0.08], [0.32, 0.23, 0.14], [0.42, 0.32, 0.2]], [0.0, 0.35, 0.7, 1.0], 3.0, 0.0, (0.9, 1.0), 0.0, 2.5)),
676        p("Ground", "Mud", natural([[0.06, 0.04, 0.025], [0.13, 0.09, 0.05], [0.2, 0.14, 0.08], [0.28, 0.2, 0.12]], [0.0, 0.4, 0.75, 1.0], 2.0, 2.0, (0.3, 0.7), 0.0, 3.0)),
677        p("Ground", "Sand", natural(sand, [0.0, 0.35, 0.7, 1.0], 8.0, 0.0, (0.85, 1.0), 0.0, 1.0)),
678        p("Ground", "Grass", natural([[0.04, 0.12, 0.03], [0.08, 0.25, 0.05], [0.16, 0.38, 0.08], [0.3, 0.5, 0.14]], [0.0, 0.35, 0.7, 1.0], 9.0, 0.0, (0.8, 1.0), 0.0, 1.2)),
679        p("Ground", "Moss", natural([[0.03, 0.1, 0.03], [0.1, 0.25, 0.06], [0.2, 0.4, 0.1], [0.35, 0.55, 0.2]], [0.0, 0.3, 0.65, 1.0], 14.0, 2.0, (0.9, 1.0), 0.0, 2.5)),
680        p("Ground", "Snow", natural([[0.7, 0.75, 0.82], [0.85, 0.9, 0.96], [0.95, 0.97, 1.0], [1.0, 1.0, 1.0]], [0.0, 0.3, 0.7, 1.0], 4.0, 0.0, (0.55, 0.8), 0.0, 0.6)),
681        p("Ground", "Ice", natural([[0.45, 0.65, 0.8], [0.62, 0.8, 0.92], [0.8, 0.92, 0.98], [0.95, 1.0, 1.0]], [0.0, 0.35, 0.7, 1.0], 3.0, 1.0, (0.05, 0.25), 0.0, 0.5)),
682        p("Ground", "Dry Earth Cracks", cracked([0.5, 0.38, 0.24], [0.06, 0.04, 0.02], 4.0, 0.95)),
683        // ---- metal
684        p("Metal", "Brushed Steel", metal_brushed([0.75, 0.77, 0.8], [0.5, 0.52, 0.55], 0.35, 180.0)),
685        p("Metal", "Brushed Aluminium", metal_brushed([0.88, 0.89, 0.9], [0.65, 0.66, 0.68], 0.3, 220.0)),
686        p("Metal", "Copper", metal_brushed([0.95, 0.52, 0.38], [0.6, 0.3, 0.2], 0.3, 120.0)),
687        p("Metal", "Gold", metal_brushed([1.0, 0.78, 0.34], [0.75, 0.55, 0.2], 0.2, 90.0)),
688        p("Metal", "Titanium", metal_brushed([0.55, 0.52, 0.5], [0.38, 0.36, 0.35], 0.38, 160.0)),
689        p("Metal", "Rusty Iron", natural([[0.1, 0.1, 0.1], [0.4, 0.17, 0.07], [0.6, 0.28, 0.1], [0.35, 0.33, 0.32]], [0.0, 0.35, 0.65, 1.0], 4.0, 1.0, (0.55, 0.95), 0.6, 3.0)),
690        p("Metal", "Hexagon Plate", hex_metal([0.5, 0.52, 0.56], [0.12, 0.12, 0.14], 7.0)),
691        p("Metal", "Carbon Fiber", weave([0.02, 0.02, 0.025], [0.07, 0.07, 0.08], 24.0, 0.3, 0.3)),
692        p("Metal", "Chain Mail", hex_metal([0.4, 0.42, 0.45], [0.05, 0.05, 0.06], 18.0)),
693        // ---- wood and organic
694        p("Wood", "Oak", wood([0.55, 0.38, 0.2], [0.3, 0.19, 0.09], false)),
695        p("Wood", "Dark Walnut", wood([0.25, 0.14, 0.08], [0.1, 0.055, 0.03], false)),
696        p("Wood", "Pine Planks", wood([0.78, 0.6, 0.36], [0.55, 0.38, 0.2], true)),
697        p("Wood", "Floor Boards", wood([0.45, 0.3, 0.17], [0.22, 0.13, 0.07], true)),
698        p("Organic", "Leather", cracked([0.22, 0.1, 0.05], [0.08, 0.035, 0.02], 22.0, 0.55)),
699        p("Organic", "Skin", natural([[0.55, 0.32, 0.25], [0.72, 0.46, 0.36], [0.85, 0.6, 0.48], [0.9, 0.68, 0.56]], [0.0, 0.35, 0.7, 1.0], 18.0, 0.0, (0.45, 0.6), 0.0, 0.4)),
700        p("Organic", "Bark", natural([[0.06, 0.045, 0.035], [0.15, 0.11, 0.08], [0.26, 0.19, 0.13], [0.36, 0.28, 0.2]], [0.0, 0.35, 0.7, 1.0], 3.0, 1.0, (0.9, 1.0), 0.0, 5.0)),
701        // ---- fabric, tiles, bricks
702        p("Fabric", "Linen", weave([0.72, 0.68, 0.58], [0.6, 0.56, 0.47], 40.0, 0.0, 0.95)),
703        p("Fabric", "Denim", weave([0.08, 0.14, 0.3], [0.12, 0.2, 0.4], 60.0, 0.0, 0.95)),
704        p("Fabric", "Red Cloth", weave([0.5, 0.04, 0.04], [0.65, 0.07, 0.06], 50.0, 0.0, 0.85)),
705        p("Building", "Red Bricks", bricks([0.55, 0.2, 0.12], [0.55, 0.53, 0.5])),
706        p("Building", "Old Bricks", bricks([0.4, 0.22, 0.16], [0.3, 0.29, 0.28])),
707        p("Building", "Checker Tiles", tiles([0.9, 0.9, 0.88], [0.1, 0.1, 0.12], 8.0, 0.04, 0.2, 0.0)),
708        p("Building", "Bathroom Tiles", tiles([0.85, 0.9, 0.92], [0.78, 0.84, 0.88], 12.0, 0.05, 0.12, 0.0)),
709        p("Building", "Plaster", natural([[0.7, 0.68, 0.64], [0.78, 0.76, 0.72], [0.85, 0.83, 0.79], [0.9, 0.88, 0.85]], [0.0, 0.4, 0.7, 1.0], 7.0, 0.0, (0.85, 1.0), 0.0, 0.5)),
710        // ---- emissive and sci-fi
711        p("Emissive", "Lava", emissive([[0.04, 0.0, 0.0], [0.6, 0.06, 0.0], [1.0, 0.45, 0.05], [1.0, 0.9, 0.4]], [0.2, 0.45, 0.7, 1.0], 2.5, 1.0, 4.0, [0.04, 0.03, 0.03], 0.9)),
712        p("Emissive", "Toxic Slime", emissive([[0.0, 0.05, 0.0], [0.05, 0.4, 0.0], [0.3, 0.9, 0.05], [0.8, 1.0, 0.4]], [0.2, 0.5, 0.75, 1.0], 3.0, 2.0, 2.5, [0.04, 0.12, 0.03], 0.15)),
713        p("Emissive", "Plasma", emissive([[0.05, 0.0, 0.2], [0.4, 0.0, 0.9], [0.9, 0.3, 1.0], [1.0, 0.9, 1.0]], [0.1, 0.4, 0.7, 1.0], 4.0, 2.0, 3.0, [0.02, 0.0, 0.05], 0.3)),
714        p("Emissive", "Neon Grid Cyan", neon_grid([0.0, 0.9, 1.0], 6.0)),
715        p("Emissive", "Neon Grid Magenta", neon_grid([1.0, 0.1, 0.8], 6.0)),
716        // ---- plain and translucent
717        p("Plain", "Glass Clear", glass([0.9, 0.95, 1.0], 0.12, 0.03)),
718        p("Plain", "Glass Green", glass([0.3, 0.7, 0.45], 0.25, 0.05)),
719        p("Plain", "Rubber Black", plain([0.02, 0.02, 0.022], 0.85, 0.0)),
720        p("Plain", "Plastic White", plain([0.9, 0.9, 0.9], 0.35, 0.0)),
721        p("Plain", "Plastic Red", plain([0.8, 0.04, 0.03], 0.3, 0.0)),
722        p("Plain", "Plastic Blue", plain([0.05, 0.2, 0.8], 0.3, 0.0)),
723        p("Plain", "Chrome", plain([0.9, 0.9, 0.92], 0.04, 1.0)),
724        p("Plain", "Clay", plain([0.55, 0.3, 0.2], 0.9, 0.0)),
725        // ---- stylized / cartoon
726        p("Stylized", "Toon Grass", stylized([[0.1, 0.4, 0.15], [0.2, 0.6, 0.2], [0.4, 0.8, 0.25], [0.65, 0.92, 0.35]], 3.0, 4.0, 0.0)),
727        p("Stylized", "Toon Stone", stylized([[0.32, 0.33, 0.4], [0.5, 0.52, 0.6], [0.66, 0.68, 0.76], [0.82, 0.84, 0.9]], 2.5, 4.0, 1.0)),
728        p("Stylized", "Toon Sand", stylized([[0.82, 0.62, 0.35], [0.92, 0.75, 0.45], [0.98, 0.86, 0.58], [1.0, 0.94, 0.75]], 4.0, 4.0, 0.0)),
729        p("Stylized", "Toon Snow", stylized([[0.6, 0.72, 0.9], [0.78, 0.88, 1.0], [0.92, 0.96, 1.0], [1.0, 1.0, 1.0]], 3.0, 3.0, 0.0)),
730        p("Stylized", "Toon Dirt", stylized([[0.25, 0.15, 0.1], [0.38, 0.24, 0.15], [0.5, 0.33, 0.2], [0.62, 0.42, 0.26]], 3.5, 4.0, 2.0)),
731        p("Stylized", "Toon Wood", wood([0.8, 0.55, 0.3], [0.5, 0.3, 0.15], false)),
732        p("Stylized", "Toon Water Foam", stylized([[0.1, 0.4, 0.75], [0.2, 0.6, 0.9], [0.55, 0.85, 1.0], [0.95, 1.0, 1.0]], 2.0, 5.0, 2.0)),
733        p("Stylized", "Toon Lava", stylized([[0.3, 0.02, 0.02], [0.8, 0.15, 0.05], [1.0, 0.55, 0.1], [1.0, 0.9, 0.35]], 2.5, 4.0, 1.0)),
734        p("Stylized", "Candy Stripes", pattern("Stripes", &[10.0, 0.9], [0.95, 0.2, 0.3], [1.0, 0.95, 0.95], 0.35)),
735        p("Stylized", "Polka Dots", pattern("Dots", &[8.0, 0.28], [1.0, 0.85, 0.9], [0.9, 0.2, 0.5], 0.5)),
736        p("Stylized", "Pastel Checker", pattern("Checker", &[10.0], [1.0, 0.85, 0.9], [0.8, 0.9, 1.0], 0.6)),
737        p("Stylized", "Comic Halftone", pattern("Dots", &[40.0, 0.35], [1.0, 0.96, 0.85], [0.1, 0.12, 0.3], 0.9)),
738        p("Stylized", "Zebra", pattern("Stripes", &[14.0, 0.95], [0.03, 0.03, 0.03], [0.96, 0.96, 0.96], 0.6)),
739        p("Stylized", "Honeycomb", pattern("Hexagons", &[9.0, 0.12], [0.35, 0.2, 0.02], [1.0, 0.72, 0.1], 0.3)),
740        p("Stylized", "Pixel Camo", stylized([[0.2, 0.25, 0.12], [0.35, 0.4, 0.2], [0.5, 0.42, 0.28], [0.15, 0.17, 0.1]], 5.0, 3.0, 0.0)),
741        p("Stylized", "Tie Dye", rainbow(false, 2.0)),
742        p("Stylized", "Holographic Foil", rainbow(true, 1.0)),
743        p("Stylized", "Sunset Gradient", gradient("Linear gradient", [[0.15, 0.05, 0.35], [0.75, 0.15, 0.4], [1.0, 0.55, 0.2], [1.0, 0.9, 0.5]], 0.0)),
744        p("Stylized", "Ocean Gradient", gradient("Radial gradient", [[0.0, 0.05, 0.2], [0.0, 0.3, 0.6], [0.1, 0.6, 0.8], [0.7, 0.95, 1.0]], 0.0)),
745        p("Stylized", "Neon Gradient", gradient("Angular gradient", [[1.0, 0.1, 0.6], [0.5, 0.1, 1.0], [0.1, 0.8, 1.0], [1.0, 0.1, 0.6]], 2.5)),
746        p("Stylized", "Galaxy", galaxy([0.25, 0.1, 0.6], [0.8, 0.3, 0.7])),
747        p("Stylized", "Nebula", galaxy([0.05, 0.35, 0.6], [0.2, 0.8, 0.7])),
748        p("Stylized", "Watercolor", stylized([[0.85, 0.9, 0.95], [0.7, 0.82, 0.92], [0.95, 0.85, 0.8], [0.98, 0.95, 0.9]], 1.5, 8.0, 0.0)),
749        p("Stylized", "Paper", natural([[0.86, 0.84, 0.78], [0.9, 0.88, 0.82], [0.93, 0.91, 0.86], [0.96, 0.95, 0.9]], [0.0, 0.4, 0.7, 1.0], 18.0, 0.0, (0.9, 1.0), 0.0, 0.2)),
750        p("Stylized", "Cardboard", natural([[0.5, 0.36, 0.22], [0.6, 0.45, 0.28], [0.68, 0.52, 0.34], [0.74, 0.58, 0.4]], [0.0, 0.4, 0.7, 1.0], 20.0, 1.0, (0.9, 1.0), 0.0, 0.5)),
751    ];
752    extra(&mut v);
753    v.extend(crate::material_presets_v4::library());
754    for preset in &mut v {
755        preset.graph.next_id = preset.graph.nodes.iter().map(|n| n.id + 1).max().unwrap_or(1);
756        for n in &mut preset.graph.nodes {
757            n.preview = false;
758        }
759    }
760    v
761}
762
763#[cfg(test)]
764mod tests {
765    use super::*;
766
767    #[test]
768    fn names_are_unique_and_every_preset_round_trips() {
769        let all = library();
770        let mut seen = std::collections::HashSet::new();
771        for p in &all {
772            assert!(seen.insert((p.category, p.name)), "duplicate {} / {}", p.category, p.name);
773            let again = MaterialGraph::from_json(&p.graph.to_json()).unwrap();
774            assert_eq!(again, p.graph, "{}", p.name);
775            again.validate().unwrap_or_else(|e| panic!("{}: {e}", p.name));
776        }
777    }
778}