Skip to main content

kvas_engine/
material_presets_v4.rs

1//! Game-ready materials built only from general nodes (texture coordinates, noise, Voronoi,
2//! bricks, maths, ramps, mixing and bump), so every step can be opened and changed in the node
3//! editor. They go to the `Game Ready` folder of the material library; marble is built here too.
4//!
5//! ```
6//! let all = kvas_engine::material_presets_v4::library();
7//! assert!(all.len() >= 30);
8//! assert!(all.iter().all(|p| kvas_engine::material_graph::to_wgsl(&p.graph).is_ok()));
9//! ```
10
11use crate::material_graph::{GraphMode, MaterialGraph};
12use crate::material_presets::Preset;
13
14const BASE: usize = 0;
15const ROUGH: usize = 1;
16const METAL: usize = 2;
17const EMIT: usize = 3;
18const AO: usize = 5;
19const NORMAL: usize = 6;
20
21// Outputs used below.
22const WORLD: u8 = 2; // Texture coordinate > World
23const UV: u8 = 0;
24const FACTOR: u8 = 0;
25const COLOUR: u8 = 1; // Noise texture > Colour
26const EDGE: u8 = 2; // Voronoi texture > Edge
27const CELL: u8 = 3; // Voronoi texture > Cell value
28const MASK: u8 = 1; // Brick texture > Mask
29const BRICK_VALUE: u8 = 2;
30const SLOPE: u8 = 5; // Geometry info > Slope
31const NORMAL_UP: u8 = 1; // Normal > Up facing
32
33// Math operations.
34const ADD: f32 = 0.0;
35const SUB: f32 = 1.0;
36const MUL: f32 = 2.0;
37const POW: f32 = 4.0;
38const ABS: f32 = 7.0;
39const SIN: f32 = 17.0;
40const MULADD: f32 = 25.0;
41
42// Mix colour modes.
43const MIX: f32 = 0.0;
44const MULTIPLY: f32 = 1.0;
45const OVERLAY: f32 = 3.0;
46
47/// Graph builder: nodes are stacked in columns from left to right.
48struct G {
49    g: MaterialGraph,
50    out: u32,
51    col: [f32; 8],
52}
53
54impl G {
55    fn new() -> Self {
56        let mut g = MaterialGraph { nodes: Vec::new(), textures: Default::default(), next_id: 1, mode: GraphMode::Material };
57        let out = g.add_node("Output", [1900.0, 140.0]);
58        G { g, out, col: [0.0; 8] }
59    }
60
61    fn node(&mut self, kind: &str, column: usize, params: &[f32]) -> u32 {
62        let c = column.min(7);
63        let y = self.col[c];
64        let id = self.g.add_node(kind, [40.0 + c as f32 * 260.0, 30.0 + y]);
65        let n = self.g.nodes.iter_mut().find(|n| n.id == id).unwrap();
66        for (i, v) in params.iter().enumerate() {
67            n.params[i] = *v;
68        }
69        let h = 60.0 + (n.inputs.len() + n.params.len() + crate::material_nodes_v4::outputs(kind).len()) as f32 * 24.0;
70        self.col[c] += h.max(120.0);
71        id
72    }
73
74    fn value(&mut self, id: u32, input: usize, v: [f32; 3]) {
75        let n = self.g.nodes.iter_mut().find(|n| n.id == id).unwrap();
76        n.inputs[input].value = v;
77        n.inputs[input].override_default = true;
78    }
79
80    fn num(&mut self, id: u32, input: usize, v: f32) {
81        self.value(id, input, [v; 3]);
82    }
83
84    /// Links output `out` of `from` to `input` of `to`, converting with `convert` (0 = as is).
85    fn link(&mut self, from: u32, out: u8, to: u32, input: usize, convert: u8) {
86        assert!(self.g.connect_output(from, out, to, input, convert), "link {from}.{out} -> {to}.{input}");
87    }
88
89    fn out(&mut self, from: u32, out: u8, socket: usize) {
90        let o = self.out;
91        self.link(from, out, o, socket, 0);
92    }
93
94    fn out_value(&mut self, socket: usize, v: f32) {
95        let o = self.out;
96        self.num(o, socket, v);
97    }
98
99    /// World coordinates scaled by `scale` (Texture coordinate > World into a Mapping node).
100    fn world(&mut self, scale: f32) -> u32 {
101        let tc = self.node("Texture coordinate", 0, &[]);
102        let m = self.node("Mapping", 0, &[0.0, 0.0, 0.0, 0.0, scale, scale, scale]);
103        self.link(tc, WORLD, m, 0, 0);
104        m
105    }
106
107    fn uv(&mut self, scale: f32) -> u32 {
108        let tc = self.node("Texture coordinate", 0, &[]);
109        let m = self.node("Mapping", 0, &[0.0, 0.0, 0.0, 0.0, scale, scale, 1.0]);
110        self.link(tc, UV, m, 0, 0);
111        m
112    }
113
114    fn noise(&mut self, coords: u32, scale: f32, detail: f32, roughness: f32, distortion: f32, column: usize) -> u32 {
115        let n = self.node("Noise texture", column, &[0.0, 2.0]);
116        self.link(coords, 0, n, 0, 0);
117        self.num(n, 1, scale);
118        self.num(n, 2, detail);
119        self.num(n, 3, roughness);
120        self.num(n, 4, distortion);
121        n
122    }
123
124    fn voronoi(&mut self, coords: u32, scale: f32, randomness: f32, column: usize) -> u32 {
125        let v = self.node("Voronoi texture", column, &[0.0]);
126        self.link(coords, 0, v, 0, 0);
127        self.num(v, 1, scale);
128        self.num(v, 2, randomness);
129        v
130    }
131
132    fn math(&mut self, op: f32, column: usize, clamp: bool) -> u32 {
133        self.node("Math", column, &[op, if clamp { 1.0 } else { 0.0 }])
134    }
135
136    fn ramp(&mut self, source: u32, out: u8, stops: [[f32; 3]; 4], pos: [f32; 4], ease: bool, column: usize) -> u32 {
137        let r = self.node("Color ramp", column, &[pos[0], pos[1], pos[2], pos[3], if ease { 2.0 } else { 0.0 }]);
138        for (i, s) in stops.iter().enumerate() {
139            self.value(r, i + 1, *s);
140        }
141        self.link(source, out, r, 0, 0);
142        r
143    }
144
145    /// `A` blended with `B` by `factor` in a mode.
146    fn mix(&mut self, mode: f32, column: usize) -> u32 {
147        self.node("Mix colour", column, &[mode, 0.0])
148    }
149
150    fn map(&mut self, source: u32, out: u8, lo: f32, hi: f32, column: usize) -> u32 {
151        let m = self.node("Map range", column, &[0.0, 1.0, lo, hi]);
152        self.link(source, out, m, 0, 0);
153        m
154    }
155
156    fn bump(&mut self, height: u32, out: u8, strength: f32, distance: f32, column: usize) -> u32 {
157        let b = self.node("Bump", column, &[0.0]);
158        self.link(height, out, b, 0, 4);
159        self.num(b, 1, strength);
160        self.num(b, 2, distance);
161        self.out(b, 0, NORMAL);
162        b
163    }
164}
165
166/// Marble from general nodes: world coordinates projected on a direction, bent by fractal noise,
167/// turned into thin veins by |sin|, coloured by a ramp, with cloudy variation and slight relief.
168pub fn marble(base: [f32; 3], vein: [f32; 3], warp: f32, frequency: f32) -> MaterialGraph {
169    let mut b = G::new();
170    let p = b.world(3.0);
171    // Direction of the veins.
172    let dir = b.node("Vector math", 1, &[7.0]);
173    b.link(p, 0, dir, 0, 0);
174    b.value(dir, 1, [0.82, 0.31, 0.48]);
175    // Turbulence that bends the veins.
176    let turb = b.noise(p, 0.9, 7.0, 0.58, 0.35, 1);
177    let along = b.math(MUL, 2, false);
178    b.link(dir, 0, along, 0, 1);
179    b.num(along, 1, frequency);
180    let phase = b.math(MULADD, 2, false);
181    b.link(turb, FACTOR, phase, 0, 0);
182    b.num(phase, 1, warp);
183    b.link(along, 0, phase, 2, 0);
184    let wave = b.math(SIN, 3, false);
185    b.link(phase, 0, wave, 0, 0);
186    let veins = b.math(ABS, 3, false);
187    b.link(wave, 0, veins, 0, 0);
188    // A second, finer and fainter vein network.
189    let fine_phase = b.math(MULADD, 2, false);
190    b.link(turb, FACTOR, fine_phase, 0, 0);
191    b.num(fine_phase, 1, warp * 2.3);
192    b.link(along, 0, fine_phase, 2, 0);
193    let fine_sin = b.math(SIN, 3, false);
194    b.link(fine_phase, 0, fine_sin, 0, 0);
195    let fine_abs = b.math(ABS, 3, false);
196    b.link(fine_sin, 0, fine_abs, 0, 0);
197    let fine_soft = b.math(POW, 4, true);
198    b.link(fine_abs, 0, fine_soft, 0, 0);
199    b.num(fine_soft, 1, 0.35);
200    let mid = [(base[0] + vein[0]) * 0.5, (base[1] + vein[1]) * 0.5, (base[2] + vein[2]) * 0.5];
201    let colour = b.ramp(veins, 0, [vein, mid, base, [base[0] * 1.03, base[1] * 1.03, base[2] * 1.02]], [0.0, 0.035, 0.16, 1.0], true, 4);
202    let faint = b.mix(MULTIPLY, 5);
203    b.num(faint, 0, 0.25);
204    b.link(colour, 0, faint, 1, 0);
205    b.link(fine_soft, 0, faint, 2, 0);
206    // Cloudy tone variation.
207    let clouds = b.noise(p, 2.4, 4.0, 0.5, 0.0, 4);
208    let cloudy = b.mix(OVERLAY, 6);
209    b.num(cloudy, 0, 0.3);
210    b.link(faint, 0, cloudy, 1, 0);
211    b.link(clouds, COLOUR, cloudy, 2, 0);
212    b.out(cloudy, 0, BASE);
213    // Polished, slightly rougher in the veins.
214    let rough = b.map(veins, 0, 0.32, 0.08, 5);
215    b.out(rough, 0, ROUGH);
216    b.out_value(METAL, 0.0);
217    b.bump(veins, 0, 0.08, 0.01, 6);
218    b.g
219}
220
221/// Stone or clay bricks with mortar, per-brick tint, dirt in the joints and moss on top faces.
222fn brick_wall(c1: [f32; 3], c2: [f32; 3], mortar: [f32; 3], scale: f32, moss: f32) -> MaterialGraph {
223    let mut b = G::new();
224    let p = b.uv(scale);
225    let bricks = b.node("Brick texture", 1, &[2.1, 0.5, 0.045, 0.03, 0.8]);
226    b.link(p, 0, bricks, 0, 0);
227    b.value(bricks, 1, c1);
228    b.value(bricks, 2, c2);
229    b.value(bricks, 3, mortar);
230    b.num(bricks, 4, 4.0);
231    let w = b.world(1.0);
232    let grime = b.noise(w, 3.0, 6.0, 0.6, 0.2, 1);
233    let dirty = b.mix(MULTIPLY, 2);
234    b.num(dirty, 0, 0.55);
235    b.link(bricks, 0, dirty, 1, 0);
236    b.link(grime, COLOUR, dirty, 2, 0);
237    // Moss where the surface faces up, broken by noise.
238    let up = b.node("Normal", 2, &[]);
239    let moss_noise = b.noise(w, 5.0, 5.0, 0.6, 0.0, 2);
240    let moss_mask = b.math(MUL, 3, true);
241    b.link(up, NORMAL_UP, moss_mask, 0, 0);
242    b.link(moss_noise, FACTOR, moss_mask, 1, 0);
243    let moss_k = b.math(MUL, 3, true);
244    b.link(moss_mask, 0, moss_k, 0, 0);
245    b.num(moss_k, 1, moss * 2.0);
246    let mossy = b.mix(MIX, 4);
247    b.link(moss_k, 0, mossy, 0, 0);
248    b.link(dirty, 0, mossy, 1, 0);
249    b.value(mossy, 2, [0.07, 0.16, 0.04]);
250    b.out(mossy, 0, BASE);
251    let rough = b.map(grime, FACTOR, 0.75, 0.98, 4);
252    b.out(rough, 0, ROUGH);
253    let ao = b.map(bricks, MASK, 0.45, 1.0, 4);
254    b.out(ao, 0, AO);
255    b.out_value(METAL, 0.0);
256    // Relief: bricks stand out of the mortar, plus fine pitting.
257    let pits = b.noise(w, 18.0, 3.0, 0.5, 0.0, 4);
258    let height = b.math(MULADD, 5, false);
259    b.link(pits, FACTOR, height, 0, 0);
260    b.num(height, 1, 0.15);
261    b.link(bricks, MASK, height, 2, 0);
262    b.bump(height, 0, 0.9, 0.06, 6);
263    b.g
264}
265
266/// Rounded cobbles from Voronoi cells, with dark gaps, per-stone colour and dirt.
267fn cobbles(stone_a: [f32; 3], stone_b: [f32; 3], gap: [f32; 3], scale: f32) -> MaterialGraph {
268    let mut b = G::new();
269    let p = b.world(1.0);
270    let jitter = b.node("Jitter coords", 1, &[3.0]);
271    b.link(p, 0, jitter, 0, 0);
272    b.num(jitter, 1, 0.12);
273    let cells = b.voronoi(jitter, scale, 0.9, 1);
274    let stone = b.mix(MIX, 2);
275    b.link(cells, CELL, stone, 0, 0);
276    b.value(stone, 1, stone_a);
277    b.value(stone, 2, stone_b);
278    let edge = b.node("Smoothstep", 2, &[0.0, 0.12]);
279    b.link(cells, EDGE, edge, 0, 0);
280    let grain = b.noise(p, 14.0, 4.0, 0.6, 0.0, 2);
281    let grainy = b.mix(OVERLAY, 3);
282    b.num(grainy, 0, 0.4);
283    b.link(stone, 0, grainy, 1, 0);
284    b.link(grain, COLOUR, grainy, 2, 0);
285    let with_gaps = b.mix(MIX, 4);
286    b.link(edge, 0, with_gaps, 0, 0);
287    b.value(with_gaps, 1, gap);
288    b.link(grainy, 0, with_gaps, 2, 0);
289    b.out(with_gaps, 0, BASE);
290    let rough = b.map(edge, 0, 1.0, 0.7, 4);
291    b.out(rough, 0, ROUGH);
292    b.out(edge, 0, AO);
293    b.out_value(METAL, 0.0);
294    let dome = b.math(POW, 5, false);
295    b.link(edge, 0, dome, 0, 0);
296    b.num(dome, 1, 0.5);
297    b.bump(dome, 0, 1.2, 0.08, 6);
298    b.g
299}
300
301/// Wooden planks: stretched noise grain coloured by a ramp, planks from the brick node.
302fn planks(light: [f32; 3], dark: [f32; 3], scale: f32, worn: f32) -> MaterialGraph {
303    let mut b = G::new();
304    let p = b.uv(scale);
305    let boards = b.node("Brick texture", 1, &[6.0, 0.37, 0.012, 0.006, 0.7]);
306    b.link(p, 0, boards, 0, 0);
307    b.num(boards, 4, 3.0);
308    // Grain: noise stretched along the planks.
309    let stretch = b.node("Mapping", 1, &[0.0, 0.0, 0.0, 0.0, 1.0, 18.0, 1.0]);
310    b.link(p, 0, stretch, 0, 0);
311    let grain = b.noise(stretch, 2.5, 6.0, 0.62, 1.2, 2);
312    let rings = b.node("Wave texture", 2, &[0.0, 0.0]);
313    b.link(stretch, 0, rings, 0, 0);
314    b.num(rings, 1, 1.4);
315    b.num(rings, 2, 6.0);
316    let both = b.math(MUL, 3, true);
317    b.link(grain, FACTOR, both, 0, 0);
318    b.link(rings, 0, both, 1, 0);
319    let wood = b.ramp(both, 0, [[dark[0] * 0.7, dark[1] * 0.7, dark[2] * 0.7], dark, light, [light[0] * 1.08, light[1] * 1.06, light[2] * 1.04]], [0.0, 0.25, 0.6, 1.0], true, 4);
320    // Each plank a little different.
321    let tone = b.node("Hue saturation value", 5, &[]);
322    b.link(wood, 0, tone, 0, 0);
323    b.link(boards, BRICK_VALUE, tone, 3, 11);
324    let gaps = b.mix(MIX, 6);
325    b.link(boards, MASK, gaps, 0, 0);
326    b.value(gaps, 1, [0.03, 0.02, 0.012]);
327    b.link(tone, 0, gaps, 2, 0);
328    b.out(gaps, 0, BASE);
329    let rough = b.map(grain, FACTOR, 0.45 + worn * 0.3, 0.8, 5);
330    b.out(rough, 0, ROUGH);
331    b.out(boards, MASK, AO);
332    b.out_value(METAL, 0.0);
333    let height = b.math(MULADD, 6, false);
334    b.link(grain, FACTOR, height, 0, 0);
335    b.num(height, 1, 0.2);
336    b.link(boards, MASK, height, 2, 0);
337    b.bump(height, 0, 0.7, 0.03, 7);
338    b.g
339}
340
341/// Painted metal with chipped paint showing rust or bare steel at the edges of noise patches.
342fn chipped_paint(paint: [f32; 3], under: [f32; 3], rusty: bool) -> MaterialGraph {
343    let mut b = G::new();
344    let p = b.world(1.0);
345    let chips = b.noise(p, 3.5, 8.0, 0.65, 0.3, 1);
346    let chip_mask = b.node("Smoothstep", 2, &[0.56, 0.6]);
347    b.link(chips, FACTOR, chip_mask, 0, 0);
348    let stains = b.noise(p, 8.0, 5.0, 0.6, 0.0, 1);
349    let metal_col = b.mix(OVERLAY, 2);
350    b.num(metal_col, 0, 0.6);
351    b.value(metal_col, 1, under);
352    b.link(stains, COLOUR, metal_col, 2, 0);
353    let col = b.mix(MIX, 3);
354    b.link(chip_mask, 0, col, 0, 0);
355    b.value(col, 1, paint);
356    b.link(metal_col, 0, col, 2, 0);
357    // Dust in crevices (where the surface faces down or sideways).
358    let geo = b.node("Geometry info", 3, &[]);
359    let dust = b.mix(MULTIPLY, 4);
360    b.link(geo, SLOPE, dust, 0, 0);
361    b.num(dust, 0, 0.15);
362    b.link(col, 0, dust, 1, 0);
363    b.value(dust, 2, [0.6, 0.55, 0.5]);
364    b.out(dust, 0, BASE);
365    let rough = b.map(chip_mask, 0, 0.35, if rusty { 0.95 } else { 0.4 }, 4);
366    b.out(rough, 0, ROUGH);
367    let metal = b.map(chip_mask, 0, 0.0, if rusty { 0.3 } else { 1.0 }, 4);
368    b.out(metal, 0, METAL);
369    b.bump(chip_mask, 0, -0.5, 0.01, 5);
370    b.g
371}
372
373/// Ground cover: two noise layers mixed by slope and height; pebbles from Voronoi.
374fn ground(low: [f32; 3], high: [f32; 3], rocks: [f32; 3], scale: f32, rough: f32) -> MaterialGraph {
375    let mut b = G::new();
376    let p = b.world(1.0);
377    let big = b.noise(p, scale * 0.35, 6.0, 0.55, 0.4, 1);
378    let mix = b.mix(MIX, 2);
379    b.link(big, FACTOR, mix, 0, 0);
380    b.value(mix, 1, low);
381    b.value(mix, 2, high);
382    let pebbles = b.voronoi(p, scale * 2.2, 1.0, 1);
383    let peb_mask = b.node("Smoothstep", 2, &[0.32, 0.22]);
384    b.link(pebbles, 1, peb_mask, 0, 0);
385    let peb_noise = b.noise(p, scale * 3.0, 3.0, 0.5, 0.0, 2);
386    let peb_sparse = b.math(MUL, 3, true);
387    b.link(peb_mask, 0, peb_sparse, 0, 0);
388    b.link(peb_noise, FACTOR, peb_sparse, 1, 0);
389    let peb_cut = b.node("Smoothstep", 3, &[0.25, 0.4]);
390    b.link(peb_sparse, 0, peb_cut, 0, 0);
391    let with_rocks = b.mix(MIX, 4);
392    b.link(peb_cut, 0, with_rocks, 0, 0);
393    b.link(mix, 0, with_rocks, 1, 0);
394    b.value(with_rocks, 2, rocks);
395    let fine = b.noise(p, scale * 6.0, 3.0, 0.6, 0.0, 4);
396    let detail = b.mix(OVERLAY, 5);
397    b.num(detail, 0, 0.35);
398    b.link(with_rocks, 0, detail, 1, 0);
399    b.link(fine, COLOUR, detail, 2, 0);
400    b.out(detail, 0, BASE);
401    let r = b.map(big, FACTOR, rough, (rough + 0.15).min(1.0), 5);
402    b.out(r, 0, ROUGH);
403    b.out_value(METAL, 0.0);
404    let height = b.math(MULADD, 5, false);
405    b.link(fine, FACTOR, height, 0, 0);
406    b.num(height, 1, 0.3);
407    b.link(peb_cut, 0, height, 2, 0);
408    b.bump(height, 0, 0.8, 0.04, 6);
409    b.g
410}
411
412/// Glowing cracks: Voronoi edges lit from inside, dark crust in between.
413fn glowing_cracks(crust: [f32; 3], glow: [f32; 3], scale: f32, intensity: f32) -> MaterialGraph {
414    let mut b = G::new();
415    let p = b.world(1.0);
416    let jitter = b.node("Jitter coords", 1, &[2.0]);
417    b.link(p, 0, jitter, 0, 0);
418    b.num(jitter, 1, 0.25);
419    let cells = b.voronoi(jitter, scale, 1.0, 1);
420    let crack = b.node("Smoothstep", 2, &[0.08, 0.0]);
421    b.link(cells, EDGE, crack, 0, 0);
422    let pulse = b.node("Time", 2, &[]);
423    let flicker = b.noise(p, 1.5, 3.0, 0.5, 0.0, 2);
424    let live = b.math(MULADD, 3, false);
425    b.link(pulse, 1, live, 0, 0);
426    b.num(live, 1, 0.35);
427    b.link(flicker, FACTOR, live, 2, 0);
428    let heat = b.math(MUL, 4, false);
429    b.link(crack, 0, heat, 0, 0);
430    b.link(live, 0, heat, 1, 0);
431    let fire = b.ramp(heat, 0, [[0.0; 3], [glow[0] * 0.4, glow[1] * 0.1, 0.0], glow, [1.0, 0.95, 0.7]], [0.0, 0.3, 0.7, 1.0], true, 5);
432    let bright = b.math(MUL, 6, false);
433    b.link(fire, 0, bright, 0, 0);
434    b.num(bright, 1, intensity);
435    b.out(bright, 0, EMIT);
436    let rock = b.noise(p, scale * 2.0, 6.0, 0.6, 0.0, 3);
437    let base = b.mix(MULTIPLY, 4);
438    b.num(base, 0, 0.7);
439    b.value(base, 1, crust);
440    b.link(rock, COLOUR, base, 2, 0);
441    b.out(base, 0, BASE);
442    let r = b.map(crack, 0, 0.9, 0.5, 5);
443    b.out(r, 0, ROUGH);
444    b.out_value(METAL, 0.0);
445    b.bump(crack, 0, -1.2, 0.05, 6);
446    b.g
447}
448
449/// Car paint: base colour with metal flakes and a clear coat sheen at grazing angles.
450fn car_paint(colour: [f32; 3], flake: f32) -> MaterialGraph {
451    let mut b = G::new();
452    let p = b.world(1.0);
453    let flakes = b.voronoi(p, 260.0, 1.0, 1);
454    let sparkle = b.math(POW, 2, false);
455    b.link(flakes, CELL, sparkle, 0, 0);
456    b.num(sparkle, 1, 8.0);
457    let lw = b.node("Layer weight", 1, &[]);
458    b.num(lw, 0, 0.35);
459    let body = b.mix(MIX, 3);
460    b.link(lw, 1, body, 0, 8);
461    b.value(body, 1, [colour[0] * 0.35, colour[1] * 0.35, colour[2] * 0.35]);
462    b.value(body, 2, colour);
463    let flaky = b.mix(ADD, 4);
464    b.node_param(flaky, 0, 4.0);
465    b.link(sparkle, 0, flaky, 0, 0);
466    b.num(flaky, 0, flake);
467    b.link(body, 0, flaky, 1, 0);
468    b.value(flaky, 2, [0.25; 3]);
469    b.out(flaky, 0, BASE);
470    let r = b.map(sparkle, 0, 0.18, 0.05, 4);
471    b.out(r, 0, ROUGH);
472    b.out_value(METAL, 0.6);
473    b.g
474}
475
476impl G {
477    fn node_param(&mut self, id: u32, k: usize, v: f32) {
478        self.g.nodes.iter_mut().find(|n| n.id == id).unwrap().params[k] = v;
479    }
480}
481
482/// Fabric weave from two crossing wave textures, with fuzz.
483fn fabric(a: [f32; 3], b_col: [f32; 3], scale: f32) -> MaterialGraph {
484    let mut b = G::new();
485    let p = b.uv(scale);
486    let sep = b.node("Separate XYZ", 1, &[]);
487    b.link(p, 0, sep, 0, 0);
488    let warp_threads = b.math(SIN, 2, false);
489    b.link(sep, 1, warp_threads, 0, 0);
490    let weft_threads = b.math(SIN, 2, false);
491    b.link(sep, 2, weft_threads, 0, 0);
492    let scale_x = b.math(MUL, 1, false);
493    b.link(sep, 1, scale_x, 0, 0);
494    b.num(scale_x, 1, 28.0);
495    let scale_y = b.math(MUL, 1, false);
496    b.link(sep, 2, scale_y, 0, 0);
497    b.num(scale_y, 1, 28.0);
498    b.link(scale_x, 0, warp_threads, 0, 0);
499    b.link(scale_y, 0, weft_threads, 0, 0);
500    let cross = b.math(MUL, 3, false);
501    b.link(warp_threads, 0, cross, 0, 0);
502    b.link(weft_threads, 0, cross, 1, 0);
503    let weave = b.math(ABS, 3, false);
504    b.link(cross, 0, weave, 0, 0);
505    let cloth = b.mix(MIX, 4);
506    b.link(weave, 0, cloth, 0, 0);
507    b.value(cloth, 1, a);
508    b.value(cloth, 2, b_col);
509    let w = b.world(1.0);
510    let fuzz = b.noise(w, 40.0, 3.0, 0.6, 0.0, 4);
511    let fuzzy = b.mix(OVERLAY, 5);
512    b.num(fuzzy, 0, 0.3);
513    b.link(cloth, 0, fuzzy, 1, 0);
514    b.link(fuzz, COLOUR, fuzzy, 2, 0);
515    b.out(fuzzy, 0, BASE);
516    b.out_value(ROUGH, 0.92);
517    b.out_value(METAL, 0.0);
518    b.bump(weave, 0, 0.4, 0.01, 5);
519    b.g
520}
521
522/// Ceramic floor tiles with marble-like veins built in, glossy, with darker grout.
523fn tiles_marble(base: [f32; 3], vein: [f32; 3], grout: [f32; 3], scale: f32) -> MaterialGraph {
524    let g = marble(base, vein, 3.0, 2.2);
525    // Put a tile grid over the marble: reuse the builder on the existing graph.
526    let out = g.nodes.iter().find(|n| n.kind == "Output").unwrap().id;
527    let base_link = g.nodes.iter().find(|n| n.id == out).unwrap().inputs[BASE].link.unwrap();
528    let mut b = G { g, out, col: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 900.0] };
529    let p = b.uv(scale);
530    let grid = b.node("Brick texture", 7, &[1.0, 0.0, 0.02, 0.006, 0.0]);
531    b.link(p, 0, grid, 0, 0);
532    b.num(grid, 4, 4.0);
533    let tiled = b.mix(MIX, 7);
534    b.link(grid, MASK, tiled, 0, 0);
535    b.value(tiled, 1, grout);
536    b.link(base_link, 0, tiled, 2, 0);
537    b.out(tiled, 0, BASE);
538    let rough = b.map(grid, MASK, 0.8, 0.06, 7);
539    b.out(rough, 0, ROUGH);
540    b.bump(grid, MASK, 0.5, 0.02, 7);
541    b.g
542}
543
544/// Stylised hand-painted rock: posterised noise with cool shadows and warm highlights.
545fn painted_rock(dark: [f32; 3], light: [f32; 3]) -> MaterialGraph {
546    let mut b = G::new();
547    let p = b.world(1.0);
548    let n = b.noise(p, 1.8, 3.0, 0.5, 0.6, 1);
549    let ramp = b.node("Color ramp", 2, &[0.0, 0.4, 0.62, 1.0, 1.0]);
550    b.link(n, FACTOR, ramp, 0, 0);
551    b.value(ramp, 1, dark);
552    b.value(ramp, 2, [(dark[0] + light[0]) * 0.45, (dark[1] + light[1]) * 0.45, (dark[2] + light[2]) * 0.5]);
553    b.value(ramp, 3, light);
554    b.value(ramp, 4, [light[0] * 1.15, light[1] * 1.12, light[2] * 1.0]);
555    let up = b.node("Normal", 2, &[]);
556    let top = b.mix(MIX, 3);
557    b.link(up, NORMAL_UP, top, 0, 0);
558    b.link(ramp, 0, top, 1, 0);
559    b.value(top, 2, [light[0] * 1.2, light[1] * 1.25, light[2] * 1.1]);
560    b.out(top, 0, BASE);
561    b.out_value(ROUGH, 0.85);
562    b.out_value(METAL, 0.0);
563    b.bump(n, FACTOR, 0.6, 0.1, 4);
564    b.g
565}
566
567/// Ice with deep blue cracks and frosty, rougher patches.
568fn ice(clear: [f32; 3], deep: [f32; 3]) -> MaterialGraph {
569    let mut b = G::new();
570    let p = b.world(1.0);
571    let cracks = b.voronoi(p, 3.0, 1.0, 1);
572    let crack = b.node("Smoothstep", 2, &[0.04, 0.0]);
573    b.link(cracks, EDGE, crack, 0, 0);
574    let frost = b.noise(p, 6.0, 6.0, 0.6, 0.2, 1);
575    let lw = b.node("Layer weight", 1, &[]);
576    b.num(lw, 0, 0.4);
577    let body = b.mix(MIX, 3);
578    b.link(lw, 0, body, 0, 0);
579    b.value(body, 1, deep);
580    b.value(body, 2, clear);
581    let cracked = b.mix(MIX, 4);
582    b.link(crack, 0, cracked, 0, 0);
583    b.link(body, 0, cracked, 1, 0);
584    b.value(cracked, 2, [0.9, 0.97, 1.0]);
585    b.out(cracked, 0, BASE);
586    let r = b.map(frost, FACTOR, 0.02, 0.45, 4);
587    b.out(r, 0, ROUGH);
588    b.out_value(METAL, 0.0);
589    let glow = b.math(MUL, 4, false);
590    b.link(lw, 0, glow, 0, 0);
591    b.value(glow, 1, [deep[0] * 0.3, deep[1] * 0.4, deep[2] * 0.6]);
592    b.out(glow, 0, EMIT);
593    b.bump(crack, 0, 0.6, 0.02, 5);
594    b.g
595}
596
597/// Sci-fi panels: a grid of plates, glowing seams and small vents.
598fn scifi(plate: [f32; 3], seam: [f32; 3], glow: f32) -> MaterialGraph {
599    let mut b = G::new();
600    let p = b.uv(3.0);
601    let panels = b.node("Brick texture", 1, &[1.6, 0.0, 0.02, 0.004, 0.4]);
602    b.link(p, 0, panels, 0, 0);
603    b.num(panels, 4, 3.0);
604    let small = b.node("Brick texture", 1, &[1.0, 0.0, 0.035, 0.004, 0.0]);
605    b.link(p, 0, small, 0, 0);
606    b.num(small, 4, 12.0);
607    let tone = b.mix(MIX, 2);
608    b.link(panels, BRICK_VALUE, tone, 0, 11);
609    b.value(tone, 1, plate);
610    b.value(tone, 2, [plate[0] * 0.75, plate[1] * 0.75, plate[2] * 0.8]);
611    let col = b.mix(MULTIPLY, 3);
612    b.link(small, MASK, col, 0, 8);
613    b.num(col, 0, 0.25);
614    b.link(tone, 0, col, 1, 0);
615    b.value(col, 2, [0.4; 3]);
616    let seams = b.math(SUB, 3, true);
617    b.num(seams, 0, 1.0);
618    b.link(panels, MASK, seams, 1, 0);
619    b.out(col, 0, BASE);
620    let time = b.node("Time", 2, &[]);
621    let pulse = b.math(MULADD, 4, false);
622    b.link(time, 1, pulse, 0, 0);
623    b.num(pulse, 1, 0.5);
624    b.num(pulse, 2, 0.6);
625    let light = b.math(MUL, 5, false);
626    b.link(seams, 0, light, 0, 0);
627    b.link(pulse, 0, light, 1, 0);
628    let emit = b.mix(MULTIPLY, 6);
629    b.num(emit, 0, 1.0);
630    b.link(light, 0, emit, 1, 0);
631    b.value(emit, 2, [seam[0] * glow, seam[1] * glow, seam[2] * glow]);
632    b.out(emit, 0, EMIT);
633    let r = b.map(panels, BRICK_VALUE, 0.25, 0.5, 4);
634    b.out(r, 0, ROUGH);
635    b.out_value(METAL, 0.85);
636    let height = b.math(ADD, 5, false);
637    b.link(panels, MASK, height, 0, 0);
638    b.link(small, MASK, height, 1, 8);
639    b.bump(height, 0, 0.6, 0.02, 6);
640    b.g
641}
642
643/// Every material of this file.
644pub fn library() -> Vec<Preset> {
645    let p = |name: &'static str, graph: MaterialGraph| Preset { category: "Game Ready", name, graph };
646    vec![
647        p("Marble Carrara", marble([0.9, 0.9, 0.88], [0.28, 0.3, 0.34], 3.0, 1.6)),
648        p("Marble Nero", marble([0.025, 0.025, 0.03], [0.75, 0.72, 0.6], 3.5, 1.4)),
649        p("Marble Verde", marble([0.04, 0.2, 0.13], [0.75, 0.85, 0.78], 4.0, 1.8)),
650        p("Marble Rosso", marble([0.42, 0.12, 0.1], [0.9, 0.8, 0.75], 3.0, 2.0)),
651        p("Marble Floor Tiles", tiles_marble([0.86, 0.85, 0.82], [0.32, 0.33, 0.36], [0.12, 0.12, 0.12], 2.0)),
652        p("Red Brick Wall", brick_wall([0.42, 0.13, 0.07], [0.3, 0.09, 0.05], [0.55, 0.52, 0.47], 3.0, 0.15)),
653        p("Mossy Stone Wall", brick_wall([0.32, 0.31, 0.29], [0.22, 0.22, 0.21], [0.35, 0.34, 0.3], 2.0, 0.9)),
654        p("Sandstone Blocks", brick_wall([0.66, 0.5, 0.31], [0.55, 0.4, 0.24], [0.6, 0.52, 0.4], 1.5, 0.05)),
655        p("Castle Wall", brick_wall([0.38, 0.37, 0.35], [0.27, 0.27, 0.27], [0.18, 0.17, 0.16], 1.2, 0.4)),
656        p("Cobblestone Path", cobbles([0.3, 0.29, 0.27], [0.45, 0.42, 0.38], [0.05, 0.05, 0.04], 3.5)),
657        p("River Pebbles", cobbles([0.42, 0.4, 0.37], [0.2, 0.18, 0.16], [0.08, 0.07, 0.05], 9.0)),
658        p("Mossy Cobbles", cobbles([0.25, 0.27, 0.22], [0.36, 0.38, 0.3], [0.06, 0.12, 0.03], 4.0)),
659        p("Oak Floor", planks([0.5, 0.33, 0.17], [0.25, 0.14, 0.06], 1.0, 0.2)),
660        p("Weathered Planks", planks([0.42, 0.38, 0.32], [0.2, 0.18, 0.15], 1.0, 0.9)),
661        p("Dark Walnut Floor", planks([0.22, 0.12, 0.06], [0.08, 0.04, 0.02], 1.2, 0.1)),
662        p("Ship Deck", planks([0.6, 0.45, 0.28], [0.32, 0.22, 0.12], 0.8, 0.6)),
663        p("Chipped Red Paint", chipped_paint([0.5, 0.06, 0.04], [0.32, 0.14, 0.06], true)),
664        p("Chipped Blue Paint", chipped_paint([0.06, 0.18, 0.42], [0.55, 0.56, 0.58], false)),
665        p("Military Green", chipped_paint([0.16, 0.2, 0.1], [0.4, 0.4, 0.42], false)),
666        p("Industrial Yellow", chipped_paint([0.75, 0.52, 0.04], [0.35, 0.16, 0.07], true)),
667        p("Forest Floor", ground([0.08, 0.05, 0.03], [0.14, 0.12, 0.05], [0.3, 0.28, 0.25], 2.0, 0.85)),
668        p("Beach Sand", ground([0.62, 0.52, 0.36], [0.78, 0.68, 0.5], [0.5, 0.46, 0.4], 3.0, 0.9)),
669        p("Gravel Road", ground([0.25, 0.24, 0.22], [0.35, 0.33, 0.3], [0.5, 0.48, 0.45], 4.0, 0.9)),
670        p("Muddy Ground", ground([0.07, 0.05, 0.03], [0.16, 0.12, 0.07], [0.25, 0.22, 0.18], 2.5, 0.35)),
671        p("Snowy Ground", ground([0.75, 0.8, 0.88], [0.95, 0.97, 1.0], [0.3, 0.3, 0.32], 2.0, 0.6)),
672        p("Lava Rock", glowing_cracks([0.05, 0.04, 0.04], [1.0, 0.35, 0.05], 2.5, 6.0)),
673        p("Magic Crystal Cracks", glowing_cracks([0.05, 0.04, 0.08], [0.45, 0.2, 1.0], 3.0, 5.0)),
674        p("Toxic Cracks", glowing_cracks([0.04, 0.05, 0.03], [0.35, 1.0, 0.1], 3.0, 4.0)),
675        p("Car Paint Red", car_paint([0.5, 0.02, 0.02], 0.35)),
676        p("Car Paint Midnight", car_paint([0.02, 0.04, 0.12], 0.5)),
677        p("Car Paint Pearl", car_paint([0.8, 0.8, 0.78], 0.25)),
678        p("Denim", fabric([0.05, 0.09, 0.2], [0.18, 0.25, 0.4], 6.0)),
679        p("Tweed", fabric([0.25, 0.18, 0.1], [0.4, 0.33, 0.22], 5.0)),
680        p("Painted Rock", painted_rock([0.18, 0.2, 0.28], [0.62, 0.55, 0.45])),
681        p("Painted Rock Warm", painted_rock([0.25, 0.15, 0.12], [0.85, 0.65, 0.42])),
682        p("Glacier Ice", ice([0.75, 0.9, 1.0], [0.05, 0.3, 0.6])),
683        p("Sci-Fi Panels", scifi([0.35, 0.37, 0.4], [0.2, 0.7, 1.0], 4.0)),
684        p("Sci-Fi Panels Orange", scifi([0.22, 0.22, 0.24], [1.0, 0.45, 0.1], 4.0)),
685    ]
686}