1const SIZE: usize = 256;
13
14fn srgb_to_linear(c: f32) -> f32 {
15 if c <= 0.04045 {
16 c / 12.92
17 } else {
18 ((c + 0.055) / 1.055).powf(2.4)
19 }
20}
21
22fn to_u8(v: f32) -> u8 {
23 (v.clamp(0.0, 1.0) * 255.0).round() as u8
24}
25
26fn diffuse(nx: f32, ny: f32, nz: f32, lx: f32, ly: f32, lz: f32) -> f32 {
28 (nx * lx + ny * ly + nz * lz).max(0.0)
29}
30
31fn specular(nx: f32, ny: f32, nz: f32, lx: f32, ly: f32, lz: f32, shininess: f32) -> f32 {
33 let hx = lx;
35 let hy = ly;
36 let hz = lz + 1.0;
37 let hlen = (hx * hx + hy * hy + hz * hz).sqrt();
38 if hlen < 1e-6 {
39 return 0.0;
40 }
41 let hx = hx / hlen;
42 let hy = hy / hlen;
43 let hz = hz / hlen;
44 let ndoth = (nx * hx + ny * hy + nz * hz).max(0.0);
45 ndoth.powf(shininess)
46}
47
48fn generate<F: Fn(f32, f32, f32) -> [u8; 4]>(f: F) -> Vec<u8> {
54 let mut data = Vec::with_capacity(SIZE * SIZE * 4);
55 for y in 0..SIZE {
56 for x in 0..SIZE {
57 let u = (x as f32 + 0.5) / SIZE as f32; let v = (y as f32 + 0.5) / SIZE as f32; let nx = u * 2.0 - 1.0;
62 let ny = 1.0 - v * 2.0; let r2 = nx * nx + ny * ny;
64 if r2 > 1.0 {
65 data.extend_from_slice(&[0, 0, 0, 0]);
67 } else {
68 let nz = (1.0 - r2).sqrt();
69 data.extend_from_slice(&f(nx, ny, nz));
70 }
71 }
72 }
73 data
74}
75
76pub fn clay() -> Vec<u8> {
85 let (mlx, mly, mlz) = {
87 let len = (0.4f32 * 0.4 + 0.9 * 0.9 + 0.4 * 0.4).sqrt();
88 (-0.4 / len, 0.9 / len, 0.4 / len)
89 };
90 let (flx, fly, flz) = {
92 let len = (0.6f32 * 0.6 + 0.2 * 0.2 + 0.3 * 0.3).sqrt();
93 (0.6 / len, -0.2 / len, 0.3 / len)
94 };
95
96 let base = [
97 srgb_to_linear(0.78),
98 srgb_to_linear(0.47),
99 srgb_to_linear(0.26),
100 ];
101 let fill_color = [
102 srgb_to_linear(0.45),
103 srgb_to_linear(0.58),
104 srgb_to_linear(0.72),
105 ];
106
107 generate(|nx, ny, nz| {
108 let d = diffuse(nx, ny, nz, mlx, mly, mlz);
109 let fd = diffuse(nx, ny, nz, flx, fly, flz) * 0.3;
110 let s = specular(nx, ny, nz, mlx, mly, mlz, 18.0) * 0.25;
111 let ambient = 0.18;
112
113 let r = base[0] * (ambient + d * 0.75) + fill_color[0] * fd + s;
114 let g = base[1] * (ambient + d * 0.75) + fill_color[1] * fd + s;
115 let b = base[2] * (ambient + d * 0.75) + fill_color[2] * fd + s;
116 let a = 0.58_f32;
118 [to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
119 })
120}
121
122pub fn wax() -> Vec<u8> {
124 let (lx, ly, lz) = {
125 let len = (0.3f32 * 0.3 + 1.0 * 1.0 + 0.5 * 0.5).sqrt();
126 (0.3 / len, 1.0 / len, 0.5 / len)
127 };
128 let base = [
129 srgb_to_linear(0.95),
130 srgb_to_linear(0.78),
131 srgb_to_linear(0.65),
132 ];
133
134 generate(|nx, ny, nz| {
135 let d = diffuse(nx, ny, nz, lx, ly, lz);
136 let s = specular(nx, ny, nz, lx, ly, lz, 8.0) * 0.55;
138 let ambient = 0.22;
139 let r = base[0] * (ambient + d * 0.68) + s * 0.95;
140 let g = base[1] * (ambient + d * 0.68) + s * 0.88;
141 let b = base[2] * (ambient + d * 0.68) + s * 0.78;
142 let a = 0.45_f32;
143 [to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
144 })
145}
146
147pub fn candy() -> Vec<u8> {
152 let (lx, ly, lz) = {
153 let len = (0.0f32 * 0.0 + 1.0 * 1.0 + 0.6 * 0.6).sqrt();
154 (0.0 / len, 1.0 / len, 0.6 / len)
155 };
156
157 generate(|nx, ny, nz| {
158 let d = diffuse(nx, ny, nz, lx, ly, lz);
159 let s = specular(nx, ny, nz, lx, ly, lz, 32.0) * 0.7;
160 let ambient = 0.25;
161
162 let angle = nx.atan2(nz) / std::f32::consts::PI; let hue = (angle * 0.5 + 0.5 + ny * 0.12).fract(); let h6 = hue * 6.0;
168 let i = h6.floor() as u32 % 6;
169 let f = h6 - h6.floor();
170 let q = 1.0 - f;
171 let (hr, hg, hb) = match i {
172 0 => (1.0_f32, f, 0.0),
173 1 => (q, 1.0, 0.0),
174 2 => (0.0, 1.0, f),
175 3 => (0.0, q, 1.0),
176 4 => (f, 0.0, 1.0),
177 _ => (1.0, 0.0, q),
178 };
179
180 let lit = (ambient + d * 0.65).min(1.0);
181 let r = hr * lit + s;
182 let g = hg * lit + s;
183 let b = hb * lit + s;
184 let a = 0.28_f32;
185 [to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
186 })
187}
188
189pub fn flat() -> Vec<u8> {
194 let (lx, ly, lz) = (0.0_f32, 1.0, 0.0);
195
196 generate(|nx, ny, nz| {
197 let d = diffuse(nx, ny, nz, lx, ly, lz);
198 let ambient = 0.20;
199 let gray = ambient + d * 0.78;
200 let a = 0.65_f32;
201 [to_u8(gray), to_u8(gray), to_u8(gray), to_u8(a)]
202 })
203}
204
205pub fn ceramic() -> Vec<u8> {
207 let (lx, ly, lz) = {
208 let len = (-0.2f32 * -0.2 + 0.85 * 0.85 + 0.5 * 0.5).sqrt();
209 (-0.2 / len, 0.85 / len, 0.5 / len)
210 };
211 let base = [
212 srgb_to_linear(0.94),
213 srgb_to_linear(0.95),
214 srgb_to_linear(0.97),
215 ];
216
217 generate(|nx, ny, nz| {
218 let d = diffuse(nx, ny, nz, lx, ly, lz);
219 let s = specular(nx, ny, nz, lx, ly, lz, 80.0) * 0.85;
220 let ambient = 0.25;
221 let r = base[0] * (ambient + d * 0.72) + s;
222 let g = base[1] * (ambient + d * 0.72) + s;
223 let b = base[2] * (ambient + d * 0.72) + s;
224 [to_u8(r), to_u8(g), to_u8(b), 255]
225 })
226}
227
228pub fn jade() -> Vec<u8> {
230 let (lx, ly, lz) = {
231 let len = (0.3f32 * 0.3 + 0.9 * 0.9 + 0.4 * 0.4).sqrt();
232 (0.3 / len, 0.9 / len, 0.4 / len)
233 };
234 let surface = [
235 srgb_to_linear(0.15),
236 srgb_to_linear(0.58),
237 srgb_to_linear(0.36),
238 ];
239 let deep = [
240 srgb_to_linear(0.04),
241 srgb_to_linear(0.28),
242 srgb_to_linear(0.18),
243 ];
244 let highlight = [
245 srgb_to_linear(0.72),
246 srgb_to_linear(0.90),
247 srgb_to_linear(0.70),
248 ];
249
250 generate(|nx, ny, nz| {
251 let d = diffuse(nx, ny, nz, lx, ly, lz);
252 let s = specular(nx, ny, nz, lx, ly, lz, 40.0) * 0.6;
253 let rim = (1.0 - nz).powf(2.5) * 0.35; let ambient = 0.18;
255
256 let t = d * 0.80 + ambient;
257 let r = deep[0] * (1.0 - t) + surface[0] * t + highlight[0] * s + 0.12 * rim;
258 let g = deep[1] * (1.0 - t) + surface[1] * t + highlight[1] * s + 0.22 * rim;
259 let b = deep[2] * (1.0 - t) + surface[2] * t + highlight[2] * s + 0.14 * rim;
260 [to_u8(r), to_u8(g), to_u8(b), 255]
261 })
262}
263
264pub fn mud() -> Vec<u8> {
266 let (lx, ly, lz) = {
267 let len = (0.1f32 * 0.1 + 0.8 * 0.8 + 0.3 * 0.3).sqrt();
268 (0.1 / len, 0.8 / len, 0.3 / len)
269 };
270 let base = [
271 srgb_to_linear(0.32),
272 srgb_to_linear(0.20),
273 srgb_to_linear(0.10),
274 ];
275 let dark = [
276 srgb_to_linear(0.08),
277 srgb_to_linear(0.05),
278 srgb_to_linear(0.02),
279 ];
280
281 generate(|nx, ny, nz| {
282 let d = diffuse(nx, ny, nz, lx, ly, lz);
283 let s = specular(nx, ny, nz, lx, ly, lz, 4.0) * 0.12;
284 let ambient = 0.12;
285
286 let rough = 0.5 + 0.5 * ((nx * 7.0).sin() * (ny * 5.0).cos() * 0.5);
288 let t = (ambient + d * 0.65) * rough;
289 let r = dark[0] * (1.0 - t) + base[0] * t + s;
290 let g = dark[1] * (1.0 - t) + base[1] * t + s;
291 let b = dark[2] * (1.0 - t) + base[2] * t + s;
292 [to_u8(r), to_u8(g), to_u8(b), 255]
293 })
294}
295
296pub fn normal() -> Vec<u8> {
300 generate(|nx, ny, nz| {
301 let r = nx * 0.5 + 0.5;
302 let g = ny * 0.5 + 0.5;
303 let b = nz * 0.5 + 0.5;
304 [to_u8(r), to_u8(g), to_u8(b), 255]
305 })
306}