High-res textures take on the pixel-art colours; treasury and mushroom garden closer to the pixel art
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No files matched your search
@@ -45,9 +45,9 @@ Side view, standing on the bottom edge of the image and filling most of its widt
|
||||
| steel | WALL | A wall of dark riveted iron plates with rows of round rivets along the seams, a little rust and soot. |
|
||||
| obsidian | STYLE | Cooled lava rock: black and very dark purple volcanic glass with smooth glassy facets and a few thin faintly glowing orange seams. |
|
||||
| face | WALL | A cut vertical face of packed brown earth with small stones and roots, slightly uneven. |
|
||||
| treasury | STYLE | The floor of a treasure vault: polished dark stone flagstones with inlaid golden bands and a few scattered gold coins. |
|
||||
| treasury | STYLE | The floor of a treasure vault: a checkerboard of square floor tiles in warm golden-ochre and darker bronze-brown, each tile slightly bevelled and worn, a few small gold coins lying in some joints. |
|
||||
| lair | STYLE | A monster lair floor: packed earth covered with scattered straw, old furs, a few bones and dirty rags. |
|
||||
| farm | STYLE | A mushroom garden floor: dark moist soil in tilled furrows with tiny sprouting mushrooms and spores. |
|
||||
| farm | STYLE | A mushroom garden floor: brown moist soil covered with large irregular patches of bright green moss, a few tiny pale mushrooms growing in the moss. |
|
||||
| training | STYLE | A training hall floor: worn wooden planks with sawdust, scuff marks and a few dark stains. |
|
||||
| library | STYLE | A repeating herringbone parquet of dark wooden boards, worn, with a few faint scratched arcane symbols and ink stains - no carpet, no large motif, no border. |
|
||||
| torture | STYLE | A torture chamber floor: dark stained flagstones with rusty iron drain grates and a few chain links. |
|
||||
@@ -86,3 +86,12 @@ Side view, standing on the bottom edge of the image and filling most of its widt
|
||||
|
||||
The reinforced wall of the pixel-art atlas is drawn in code (`T_REINF` in `gen_atlas`: courses of
|
||||
6, 5 and 5 pixels cut into blocks of 5-9 pixels).
|
||||
|
||||
## Closer to the pixel art (2026-11-07)
|
||||
|
||||
- Treasury and mushroom garden were made again (`seed: 4302`, the prompts in the table above)
|
||||
after the pixel-art cells: a golden checkerboard and brown soil with green moss; the first
|
||||
pictures are in `old/`.
|
||||
- The game moves the colours of every generated picture most of the way to those of the 16x16
|
||||
cells it replaces (mean and contrast per channel, `match_colors` in `src/gfx.rs`), so both
|
||||
styles keep the same colours.
|
||||
@@ -16,9 +16,9 @@ TEX = {
|
||||
"obsidian": (STYLE, "Cooled lava rock: black and very dark purple volcanic glass with smooth glassy facets and a few thin faintly glowing orange seams."),
|
||||
"face": (WALL, "A cut vertical face of packed brown earth with small stones and roots, slightly uneven."),
|
||||
# rooms
|
||||
"treasury": (STYLE, "The floor of a treasure vault: polished dark stone flagstones with inlaid golden bands and a few scattered gold coins."),
|
||||
"treasury": (STYLE, "The floor of a treasure vault: a checkerboard of square floor tiles in warm golden-ochre and darker bronze-brown, each tile slightly bevelled and worn, a few small gold coins lying in some joints."),
|
||||
"lair": (STYLE, "A monster lair floor: packed earth covered with scattered straw, old furs, a few bones and dirty rags."),
|
||||
"farm": (STYLE, "A mushroom garden floor: dark moist soil in tilled furrows with tiny sprouting mushrooms and spores."),
|
||||
"farm": (STYLE, "A mushroom garden floor: brown moist soil covered with large irregular patches of bright green moss, a few tiny pale mushrooms growing in the moss."),
|
||||
"training": (STYLE, "A training hall floor: worn wooden planks with sawdust, scuff marks and a few dark stains."),
|
||||
"library": (STYLE, "A repeating herringbone parquet of dark wooden boards, worn, with a few faint scratched arcane symbols and ink stains - no carpet, no large motif, no border."),
|
||||
"torture": (STYLE, "A torture chamber floor: dark stained flagstones with rusty iron drain grates and a few chain links."),
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 344 KiB After Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 364 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
@@ -458,6 +458,59 @@ pub fn hd_tile(id: u32, x: i32, z: i32) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
// mean and standard deviation per channel of the opaque texels of some cells
|
||||
fn cell_stats(img: &Image, cells: &[u32], tile: u32) -> ([f32; 3], [f32; 3]) {
|
||||
let (mut sum, mut sq, mut n) = ([0f32; 3], [0f32; 3], 0f32);
|
||||
for &id in cells {
|
||||
let (ox, oy) = ((id % ATLAS_COLS) * tile, (id / ATLAS_COLS) * tile);
|
||||
for y in 0..tile {
|
||||
for x in 0..tile {
|
||||
let p = img.get_pixel(ox + x, oy + y);
|
||||
if p.a < 0.5 {
|
||||
continue;
|
||||
}
|
||||
for (c, v) in [p.r, p.g, p.b].into_iter().enumerate() {
|
||||
sum[c] += v;
|
||||
sq[c] += v * v;
|
||||
}
|
||||
n += 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
let n = n.max(1.0);
|
||||
let mean = sum.map(|s| s / n);
|
||||
let mut sd = [0f32; 3];
|
||||
for c in 0..3 {
|
||||
sd[c] = (sq[c] / n - mean[c] * mean[c]).max(0.0).sqrt();
|
||||
}
|
||||
(mean, sd)
|
||||
}
|
||||
|
||||
// moves the colours of the high-res cells `dst` towards those of the 16x16 cells `src`
|
||||
fn match_colors(img: &mut Image, small: &Image, src: &[u32], dst: &[u32], tile: u32) {
|
||||
const STRENGTH: f32 = 0.8;
|
||||
let (pm, ps) = cell_stats(small, src, 16);
|
||||
let (hm, hs) = cell_stats(img, dst, tile);
|
||||
for &id in dst {
|
||||
let (ox, oy) = ((id % ATLAS_COLS) * tile, (id / ATLAS_COLS) * tile);
|
||||
for y in 0..tile {
|
||||
for x in 0..tile {
|
||||
let p = img.get_pixel(ox + x, oy + y);
|
||||
if p.a < 0.5 {
|
||||
continue;
|
||||
}
|
||||
let mut q = [p.r, p.g, p.b];
|
||||
for c in 0..3 {
|
||||
let k = (ps[c] / hs[c].max(0.01)).clamp(0.6, 1.6);
|
||||
let m = pm[c] + (q[c] - hm[c]) * k;
|
||||
q[c] = (q[c] + (m - q[c]) * STRENGTH).clamp(0.0, 1.0);
|
||||
}
|
||||
img.set_pixel(ox + x, oy + y, Color::new(q[0], q[1], q[2], p.a));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a high-res atlas with `tile` x `tile` texel cells (HD_TILE or HD_TILE2)
|
||||
fn gen_hd_atlas(tile: u32) -> Image {
|
||||
let small = gen_atlas();
|
||||
@@ -515,6 +568,25 @@ fn gen_hd_atlas(tile: u32) -> Image {
|
||||
}
|
||||
}
|
||||
}
|
||||
// the generated pictures take on the colours of the 16x16 cells they replace (mean and
|
||||
// contrast per channel, most of the way), so switching between the styles keeps the look
|
||||
let range = |a: u32, n: u32| (a..a + n).collect::<Vec<u32>>();
|
||||
let mut groups: Vec<(Vec<u32>, Vec<u32>)> = Vec::new();
|
||||
for &(base, n, _, _) in HD_TILES.iter() {
|
||||
groups.push((range(base, n), range(base, n)));
|
||||
}
|
||||
for &(first, n, own, _, _, _) in HD_SETS.iter() {
|
||||
groups.push((range(first, n), range(own, 5)));
|
||||
}
|
||||
for &(base, n, _, _) in HD_VARS.iter() {
|
||||
groups.push((range(base, n), range(base, n)));
|
||||
}
|
||||
for &(id, _, _) in HD_DECALS.iter().skip(2) {
|
||||
groups.push((vec![id], vec![id]));
|
||||
}
|
||||
for (src, dst) in groups.iter() {
|
||||
match_colors(&mut img, &small, src, dst, tile);
|
||||
}
|
||||
img
|
||||
}
|
||||
|
||||
@@ -2877,6 +2949,20 @@ mod hd_atlas_tests {
|
||||
let p = img.get_pixel(x, y);
|
||||
(p.r, p.g, p.b, p.a)
|
||||
};
|
||||
// the colours are matched to the 16x16 cells (see match_colors), but a picture keeps its
|
||||
// shapes: what is lighter in the source stays lighter in the atlas
|
||||
let placed = |hd: &Image, ox: u32, oy: u32, src: &Image, sx: u32, sy: u32| {
|
||||
let lum = |p: Color| p.r + p.g + p.b;
|
||||
let pts = [(3, 5), (7, 40), (30, 9), (50, 50), (20, 60), (61, 2)];
|
||||
for &(ax, ay) in pts.iter() {
|
||||
for &(bx, by) in pts.iter() {
|
||||
let (s1, s2) = (lum(src.get_pixel(sx + ax, sy + ay)), lum(src.get_pixel(sx + bx, sy + by)));
|
||||
if s1 + 0.15 < s2 {
|
||||
assert!(lum(hd.get_pixel(ox + ax, oy + ay)) <= lum(hd.get_pixel(ox + bx, oy + by)) + 0.02, "cell at {ox},{oy}");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// a cell without an own texture: the 16x16 one scaled up
|
||||
let (cx, cy) = ((T_PUMPKIN % ATLAS_COLS) * 16, (T_PUMPKIN / ATLAS_COLS) * 16);
|
||||
for (x, y) in [(0, 0), (5, 9), (15, 15)] {
|
||||
@@ -2891,7 +2977,7 @@ mod hd_atlas_tests {
|
||||
let id = base + v;
|
||||
let (ox, oy) = ((id % ATLAS_COLS) * tile, (id / ATLAS_COLS) * tile);
|
||||
let (sx, sy) = if n == 4 { ((v % 2) * tile, (v / 2) * tile) } else { (0, 0) };
|
||||
assert_eq!(px(&hd, ox + 7, oy + 40), px(&src, sx + 7, sy + 40));
|
||||
placed(&hd, ox, oy, &src, sx, sy);
|
||||
}
|
||||
}
|
||||
// the sets: five tiles each in their own cells (inside the atlas)
|
||||
@@ -2902,7 +2988,7 @@ mod hd_atlas_tests {
|
||||
for v in 0..5 {
|
||||
let id = own + v;
|
||||
let (ox, oy) = ((id % ATLAS_COLS) * tile, (id / ATLAS_COLS) * tile);
|
||||
assert_eq!(px(&hd, ox + 9, oy + 30), px(&src, v * tile + 9, 30));
|
||||
placed(&hd, ox, oy, &src, v * tile, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2972,6 +3058,24 @@ mod atlas_dump {
|
||||
}
|
||||
out.export_png("/tmp/atlas-cell.png");
|
||||
}
|
||||
|
||||
// `cargo test --bins dump_atlases -- --ignored`: the 16x16 atlas scaled up and the 64x64
|
||||
// high-res atlas side by side in /tmp/atlases.png (to compare the colours of the styles)
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn dump_atlases() {
|
||||
let small = gen_atlas();
|
||||
let hd = gen_hd_atlas(HD_TILE);
|
||||
let (w, h) = (hd.width as u32, hd.height as u32);
|
||||
let mut out = Image::gen_image_color((2 * w + 16) as u16, h as u16, BLACK);
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
out.set_pixel(x, h - 1 - y, small.get_pixel(x / 4, y / 4));
|
||||
out.set_pixel(w + 16 + x, h - 1 - y, hd.get_pixel(x, y));
|
||||
}
|
||||
}
|
||||
out.export_png("/tmp/atlases.png");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||