const SIZE: usize = 256;
fn srgb_to_linear(c: f32) -> f32 {
if c <= 0.04045 {
c / 12.92
} else {
((c + 0.055) / 1.055).powf(2.4)
}
}
fn to_u8(v: f32) -> u8 {
(v.clamp(0.0, 1.0) * 255.0).round() as u8
}
fn diffuse(nx: f32, ny: f32, nz: f32, lx: f32, ly: f32, lz: f32) -> f32 {
(nx * lx + ny * ly + nz * lz).max(0.0)
}
fn specular(nx: f32, ny: f32, nz: f32, lx: f32, ly: f32, lz: f32, shininess: f32) -> f32 {
let hx = lx;
let hy = ly;
let hz = lz + 1.0;
let hlen = (hx * hx + hy * hy + hz * hz).sqrt();
if hlen < 1e-6 {
return 0.0;
}
let hx = hx / hlen;
let hy = hy / hlen;
let hz = hz / hlen;
let ndoth = (nx * hx + ny * hy + nz * hz).max(0.0);
ndoth.powf(shininess)
}
fn generate<F: Fn(f32, f32, f32) -> [u8; 4]>(f: F) -> Vec<u8> {
let mut data = Vec::with_capacity(SIZE * SIZE * 4);
for y in 0..SIZE {
for x in 0..SIZE {
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;
let ny = 1.0 - v * 2.0; let r2 = nx * nx + ny * ny;
if r2 > 1.0 {
data.extend_from_slice(&[0, 0, 0, 0]);
} else {
let nz = (1.0 - r2).sqrt();
data.extend_from_slice(&f(nx, ny, nz));
}
}
}
data
}
pub fn clay() -> Vec<u8> {
let (mlx, mly, mlz) = {
let len = (0.4f32 * 0.4 + 0.9 * 0.9 + 0.4 * 0.4).sqrt();
(-0.4 / len, 0.9 / len, 0.4 / len)
};
let (flx, fly, flz) = {
let len = (0.6f32 * 0.6 + 0.2 * 0.2 + 0.3 * 0.3).sqrt();
(0.6 / len, -0.2 / len, 0.3 / len)
};
let base = [
srgb_to_linear(0.78),
srgb_to_linear(0.47),
srgb_to_linear(0.26),
];
let fill_color = [
srgb_to_linear(0.45),
srgb_to_linear(0.58),
srgb_to_linear(0.72),
];
generate(|nx, ny, nz| {
let d = diffuse(nx, ny, nz, mlx, mly, mlz);
let fd = diffuse(nx, ny, nz, flx, fly, flz) * 0.3;
let s = specular(nx, ny, nz, mlx, mly, mlz, 18.0) * 0.25;
let ambient = 0.18;
let r = base[0] * (ambient + d * 0.75) + fill_color[0] * fd + s;
let g = base[1] * (ambient + d * 0.75) + fill_color[1] * fd + s;
let b = base[2] * (ambient + d * 0.75) + fill_color[2] * fd + s;
let a = 0.58_f32;
[to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
})
}
pub fn wax() -> Vec<u8> {
let (lx, ly, lz) = {
let len = (0.3f32 * 0.3 + 1.0 * 1.0 + 0.5 * 0.5).sqrt();
(0.3 / len, 1.0 / len, 0.5 / len)
};
let base = [
srgb_to_linear(0.95),
srgb_to_linear(0.78),
srgb_to_linear(0.65),
];
generate(|nx, ny, nz| {
let d = diffuse(nx, ny, nz, lx, ly, lz);
let s = specular(nx, ny, nz, lx, ly, lz, 8.0) * 0.55;
let ambient = 0.22;
let r = base[0] * (ambient + d * 0.68) + s * 0.95;
let g = base[1] * (ambient + d * 0.68) + s * 0.88;
let b = base[2] * (ambient + d * 0.68) + s * 0.78;
let a = 0.45_f32;
[to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
})
}
pub fn candy() -> Vec<u8> {
let (lx, ly, lz) = {
let len = (0.0f32 * 0.0 + 1.0 * 1.0 + 0.6 * 0.6).sqrt();
(0.0 / len, 1.0 / len, 0.6 / len)
};
generate(|nx, ny, nz| {
let d = diffuse(nx, ny, nz, lx, ly, lz);
let s = specular(nx, ny, nz, lx, ly, lz, 32.0) * 0.7;
let ambient = 0.25;
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;
let i = h6.floor() as u32 % 6;
let f = h6 - h6.floor();
let q = 1.0 - f;
let (hr, hg, hb) = match i {
0 => (1.0_f32, f, 0.0),
1 => (q, 1.0, 0.0),
2 => (0.0, 1.0, f),
3 => (0.0, q, 1.0),
4 => (f, 0.0, 1.0),
_ => (1.0, 0.0, q),
};
let lit = (ambient + d * 0.65).min(1.0);
let r = hr * lit + s;
let g = hg * lit + s;
let b = hb * lit + s;
let a = 0.28_f32;
[to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
})
}
pub fn flat() -> Vec<u8> {
let (lx, ly, lz) = (0.0_f32, 1.0, 0.0);
generate(|nx, ny, nz| {
let d = diffuse(nx, ny, nz, lx, ly, lz);
let ambient = 0.20;
let gray = ambient + d * 0.78;
let a = 0.65_f32;
[to_u8(gray), to_u8(gray), to_u8(gray), to_u8(a)]
})
}
pub fn ceramic() -> Vec<u8> {
let (lx, ly, lz) = {
let len = (-0.2f32 * -0.2 + 0.85 * 0.85 + 0.5 * 0.5).sqrt();
(-0.2 / len, 0.85 / len, 0.5 / len)
};
let base = [
srgb_to_linear(0.94),
srgb_to_linear(0.95),
srgb_to_linear(0.97),
];
generate(|nx, ny, nz| {
let d = diffuse(nx, ny, nz, lx, ly, lz);
let s = specular(nx, ny, nz, lx, ly, lz, 80.0) * 0.85;
let ambient = 0.25;
let r = base[0] * (ambient + d * 0.72) + s;
let g = base[1] * (ambient + d * 0.72) + s;
let b = base[2] * (ambient + d * 0.72) + s;
[to_u8(r), to_u8(g), to_u8(b), 255]
})
}
pub fn jade() -> Vec<u8> {
let (lx, ly, lz) = {
let len = (0.3f32 * 0.3 + 0.9 * 0.9 + 0.4 * 0.4).sqrt();
(0.3 / len, 0.9 / len, 0.4 / len)
};
let surface = [
srgb_to_linear(0.15),
srgb_to_linear(0.58),
srgb_to_linear(0.36),
];
let deep = [
srgb_to_linear(0.04),
srgb_to_linear(0.28),
srgb_to_linear(0.18),
];
let highlight = [
srgb_to_linear(0.72),
srgb_to_linear(0.90),
srgb_to_linear(0.70),
];
generate(|nx, ny, nz| {
let d = diffuse(nx, ny, nz, lx, ly, lz);
let s = specular(nx, ny, nz, lx, ly, lz, 40.0) * 0.6;
let rim = (1.0 - nz).powf(2.5) * 0.35; let ambient = 0.18;
let t = d * 0.80 + ambient;
let r = deep[0] * (1.0 - t) + surface[0] * t + highlight[0] * s + 0.12 * rim;
let g = deep[1] * (1.0 - t) + surface[1] * t + highlight[1] * s + 0.22 * rim;
let b = deep[2] * (1.0 - t) + surface[2] * t + highlight[2] * s + 0.14 * rim;
[to_u8(r), to_u8(g), to_u8(b), 255]
})
}
pub fn mud() -> Vec<u8> {
let (lx, ly, lz) = {
let len = (0.1f32 * 0.1 + 0.8 * 0.8 + 0.3 * 0.3).sqrt();
(0.1 / len, 0.8 / len, 0.3 / len)
};
let base = [
srgb_to_linear(0.32),
srgb_to_linear(0.20),
srgb_to_linear(0.10),
];
let dark = [
srgb_to_linear(0.08),
srgb_to_linear(0.05),
srgb_to_linear(0.02),
];
generate(|nx, ny, nz| {
let d = diffuse(nx, ny, nz, lx, ly, lz);
let s = specular(nx, ny, nz, lx, ly, lz, 4.0) * 0.12;
let ambient = 0.12;
let rough = 0.5 + 0.5 * ((nx * 7.0).sin() * (ny * 5.0).cos() * 0.5);
let t = (ambient + d * 0.65) * rough;
let r = dark[0] * (1.0 - t) + base[0] * t + s;
let g = dark[1] * (1.0 - t) + base[1] * t + s;
let b = dark[2] * (1.0 - t) + base[2] * t + s;
[to_u8(r), to_u8(g), to_u8(b), 255]
})
}
pub fn normal() -> Vec<u8> {
generate(|nx, ny, nz| {
let r = nx * 0.5 + 0.5;
let g = ny * 0.5 + 0.5;
let b = nz * 0.5 + 0.5;
[to_u8(r), to_u8(g), to_u8(b), 255]
})
}