High-res tiles also in 128x128 for close views

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-07 21:29:58 +01:00
1 parent 8197b07ac5
commit 9df16607ed
24 files changed
+83 -41

No files matched your search

+11
View File
@@ -63,3 +63,14 @@ Molten lava seen from above, glowing orange and yellow molten rock flowing in sw
Water and lava flow as before (the shader moves the texture along the river and lets the surface Water and lava flow as before (the shader moves the texture along the river and lets the surface
swell); in the high-res style a second, larger layer of the same texture drifts slower and swell); in the high-res style a second, larger layer of the same texture drifts slower and
sideways over it, so the surface churns. sideways over it, so the surface churns.
## 128x128 (2026-11-07)
The same raw pictures also give 128x128 tiles for close views (`HD_TILE2`; the world shows them
when a tile gets nearly 128 pixels, else the 64x64 ones):
```bash
python3 tools/make_tile.py <name>-raw.png <name> --size 256 --colors 48 --band 28
```
`<name>-256.png` is copied to `assets/textures/<name>-256.png`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

+48 -39
View File
@@ -5,7 +5,7 @@ use crate::map::{Kind, Owner};
use macroquad::prelude::*; use macroquad::prelude::*;
pub const CELL: f32 = 16.0; pub const CELL: f32 = 16.0;
const ATLAS_COLS: u32 = 16; pub const ATLAS_COLS: u32 = 16;
pub const ATLAS_ROWS: u32 = 14; pub const ATLAS_ROWS: u32 = 14;
// atlas indices (4 variants each) // atlas indices (4 variants each)
@@ -99,7 +99,8 @@ pub struct Gfx {
pub atlas: Texture2D, pub atlas: Texture2D,
pub sprites: Texture2D, pub sprites: Texture2D,
pub hd_sprites: Texture2D, // detailed sprites (see HD_SPRITES, hd_cell_view) pub hd_sprites: Texture2D, // detailed sprites (see HD_SPRITES, hd_cell_view)
pub atlas_hd: Texture2D, // the tile atlas of the high-res style (see HD_TILES) pub atlas_hd: Texture2D, // the tile atlas of the high-res style, 64x64 tiles (see HD_TILES)
pub atlas_hd2: Texture2D, // the same with 128x128 tiles (close up)
pub cursors: Texture2D, // mouse pointer frames, 16x16 each (see CURSOR_*) pub cursors: Texture2D, // mouse pointer frames, 16x16 each (see CURSOR_*)
pub badges: Texture2D, // achievement badges, 32x32 each (see badges.rs) pub badges: Texture2D, // achievement badges, 32x32 each (see badges.rs)
pub font: Texture2D, // Dinowyde (glyphs of 6x8 px ordered by Unicode, see font_slot) and DINOBYTE pub font: Texture2D, // Dinowyde (glyphs of 6x8 px ordered by Unicode, see font_slot) and DINOBYTE
@@ -327,46 +328,49 @@ fn earth_px(x: i32, y: i32, v: u32) -> Color {
} }
// ---- high-res tiles // ---- high-res tiles
// The high-res style's atlas: the same cells as the 16x16 atlas, HD_TILE x HD_TILE texels each. // The high-res style's atlases: the same cells as the 16x16 atlas, HD_TILE (64) or HD_TILE2 (128)
// Cells without an own texture are the 16x16 ones scaled up. HD_TILES: generated textures (see // texels each; the world shows the 128 one close up, where a tile gets that many pixels (two
// art/generated/textures/), each a seamless 2x2 block of tiles for the four variants of a kind // levels like the sprites: neither loses pixels nor flickers). Cells without an own texture are
// (variant k = quarter k % 2, k / 2); the world lays the quarters out by the tiles' positions // the 16x16 ones scaled up. HD_TILES: generated textures (see art/generated/textures/), each a
// (see hd_tile), so a floor repeats only every two tiles and has no seams. // seamless 2x2 block of tiles for the four variants of a kind (variant k = quarter k % 2, k / 2);
// the world lays the quarters out by the tiles' positions (see hd_tile), so a floor repeats only
// every two tiles and has no seams.
pub const HD_TILE: u32 = 64; pub const HD_TILE: u32 = 64;
pub const ATLAS_HD_W: f32 = (ATLAS_COLS * HD_TILE) as f32; pub const HD_TILE2: u32 = 128;
pub const ATLAS_HD_H: f32 = (ATLAS_ROWS * HD_TILE) as f32; const HD_TILES: [(u32, &[u8], &[u8]); 7] = [
const HD_TILES: [(u32, &[u8]); 7] = [ (T_FLOOR, include_bytes!("../assets/textures/floor.png"), include_bytes!("../assets/textures/floor-256.png")),
(T_FLOOR, include_bytes!("../assets/textures/floor.png")), (T_CLAIM, include_bytes!("../assets/textures/claimed.png"), include_bytes!("../assets/textures/claimed-256.png")),
(T_CLAIM, include_bytes!("../assets/textures/claimed.png")), (T_EARTH, include_bytes!("../assets/textures/earth.png"), include_bytes!("../assets/textures/earth-256.png")),
(T_EARTH, include_bytes!("../assets/textures/earth.png")), (T_ROCK, include_bytes!("../assets/textures/rock.png"), include_bytes!("../assets/textures/rock-256.png")),
(T_ROCK, include_bytes!("../assets/textures/rock.png")), (T_GOLD, include_bytes!("../assets/textures/gold.png"), include_bytes!("../assets/textures/gold-256.png")),
(T_GOLD, include_bytes!("../assets/textures/gold.png")), (T_WATER, include_bytes!("../assets/textures/water.png"), include_bytes!("../assets/textures/water-256.png")),
(T_WATER, include_bytes!("../assets/textures/water.png")), (T_LAVA, include_bytes!("../assets/textures/lava.png"), include_bytes!("../assets/textures/lava-256.png")),
(T_LAVA, include_bytes!("../assets/textures/lava.png")),
]; ];
// whether a cell has a generated high-res texture (its variants are laid out by position) // whether a cell has a generated high-res texture (its variants are laid out by position)
pub fn hd_tile(id: u32) -> bool { pub fn hd_tile(id: u32) -> bool {
HD_TILES.iter().any(|&(base, _)| id / 4 * 4 == base) HD_TILES.iter().any(|&(base, _, _)| id / 4 * 4 == base)
} }
fn gen_hd_atlas() -> Image { // 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(); let small = gen_atlas();
let k = HD_TILE / 16; let k = tile / 16;
let mut img = Image::gen_image_color(ATLAS_HD_W as u16, ATLAS_HD_H as u16, Color::new(0.0, 0.0, 0.0, 0.0)); let (w, h) = (ATLAS_COLS * tile, ATLAS_ROWS * tile);
for y in 0..ATLAS_HD_H as u32 { let mut img = Image::gen_image_color(w as u16, h as u16, Color::new(0.0, 0.0, 0.0, 0.0));
for x in 0..ATLAS_HD_W as u32 { for y in 0..h {
for x in 0..w {
img.set_pixel(x, y, small.get_pixel(x / k, y / k)); img.set_pixel(x, y, small.get_pixel(x / k, y / k));
} }
} }
for &(base, png) in HD_TILES.iter() { for &(base, png, png2) in HD_TILES.iter() {
let src = Image::from_file_with_format(png, Some(ImageFormat::Png)).expect("hd tile"); let src = Image::from_file_with_format(if tile == HD_TILE2 { png2 } else { png }, Some(ImageFormat::Png)).expect("hd tile");
for v in 0..4 { for v in 0..4 {
let (sx, sy) = ((v % 2) * HD_TILE, (v / 2) * HD_TILE); let (sx, sy) = ((v % 2) * tile, (v / 2) * tile);
let id = base + v; let id = base + v;
let (ox, oy) = ((id % ATLAS_COLS) * HD_TILE, (id / ATLAS_COLS) * HD_TILE); let (ox, oy) = ((id % ATLAS_COLS) * tile, (id / ATLAS_COLS) * tile);
for y in 0..HD_TILE { for y in 0..tile {
for x in 0..HD_TILE { for x in 0..tile {
img.set_pixel(ox + x, oy + y, src.get_pixel(sx + x, sy + y)); img.set_pixel(ox + x, oy + y, src.get_pixel(sx + x, sy + y));
} }
} }
@@ -2410,11 +2414,13 @@ impl Gfx {
cursors.set_filter(FilterMode::Nearest); cursors.set_filter(FilterMode::Nearest);
let badges = Texture2D::from_image(&crate::badges::gen_badges()); let badges = Texture2D::from_image(&crate::badges::gen_badges());
badges.set_filter(FilterMode::Nearest); badges.set_filter(FilterMode::Nearest);
let atlas_hd = Texture2D::from_image(&gen_hd_atlas()); let atlas_hd = Texture2D::from_image(&gen_hd_atlas(HD_TILE));
atlas_hd.set_filter(FilterMode::Nearest); atlas_hd.set_filter(FilterMode::Nearest);
let atlas_hd2 = Texture2D::from_image(&gen_hd_atlas(HD_TILE2));
atlas_hd2.set_filter(FilterMode::Nearest);
let hd_sprites = Texture2D::from_image(&gen_hd_sprites()); let hd_sprites = Texture2D::from_image(&gen_hd_sprites());
hd_sprites.set_filter(FilterMode::Nearest); hd_sprites.set_filter(FilterMode::Nearest);
Gfx { atlas, sprites, hd_sprites, atlas_hd, cursors, badges, font, font_map } Gfx { atlas, sprites, hd_sprites, atlas_hd, atlas_hd2, cursors, badges, font, font_map }
} }
pub fn cell(id: u32) -> Rect { pub fn cell(id: u32) -> Rect {
@@ -2691,8 +2697,10 @@ mod hd_atlas_tests {
#[test] #[test]
fn high_res_atlas() { fn high_res_atlas() {
let small = gen_atlas(); let small = gen_atlas();
let hd = gen_hd_atlas(); for tile in [HD_TILE, HD_TILE2] {
assert_eq!((hd.width as f32, hd.height as f32), (ATLAS_HD_W, ATLAS_HD_H)); let hd = gen_hd_atlas(tile);
let k = tile / 16;
assert_eq!((hd.width as u32, hd.height as u32), (ATLAS_COLS * tile, ATLAS_ROWS * tile));
let px = |img: &Image, x: u32, y: u32| { let px = |img: &Image, x: u32, y: u32| {
let p = img.get_pixel(x, y); let p = img.get_pixel(x, y);
(p.r, p.g, p.b, p.a) (p.r, p.g, p.b, p.a)
@@ -2700,19 +2708,20 @@ mod hd_atlas_tests {
// a cell without an own texture: the 16x16 one scaled up // a cell without an own texture: the 16x16 one scaled up
let (cx, cy) = ((T_TREASURY % ATLAS_COLS) * 16, (T_TREASURY / ATLAS_COLS) * 16); let (cx, cy) = ((T_TREASURY % ATLAS_COLS) * 16, (T_TREASURY / ATLAS_COLS) * 16);
for (x, y) in [(0, 0), (5, 9), (15, 15)] { for (x, y) in [(0, 0), (5, 9), (15, 15)] {
assert_eq!(px(&hd, (cx + x) * 4 + 2, (cy + y) * 4 + 1), px(&small, cx + x, cy + y)); assert_eq!(px(&hd, (cx + x) * k + 2, (cy + y) * k + 1), px(&small, cx + x, cy + y));
} }
// the generated ones: the quarters of their 2x2 sheet // the generated ones: the quarters of their 2x2 sheet
for &(base, png) in HD_TILES.iter() { for &(base, png, png2) in HD_TILES.iter() {
let src = Image::from_file_with_format(png, Some(ImageFormat::Png)).unwrap(); let src = Image::from_file_with_format(if tile == HD_TILE2 { png2 } else { png }, Some(ImageFormat::Png)).unwrap();
assert_eq!((src.width, src.height), (2 * HD_TILE as u16, 2 * HD_TILE as u16)); assert_eq!((src.width as u32, src.height as u32), (2 * tile, 2 * tile));
for v in 0..4 { for v in 0..4 {
let id = base + v; let id = base + v;
assert!(hd_tile(id)); assert!(hd_tile(id));
let (ox, oy) = ((id % ATLAS_COLS) * HD_TILE, (id / ATLAS_COLS) * HD_TILE); let (ox, oy) = ((id % ATLAS_COLS) * tile, (id / ATLAS_COLS) * tile);
assert_eq!(px(&hd, ox + 7, oy + 40), px(&src, (v % 2) * HD_TILE + 7, (v / 2) * HD_TILE + 40)); assert_eq!(px(&hd, ox + 7, oy + 40), px(&src, (v % 2) * tile + 7, (v / 2) * tile + 40));
} }
} }
}
assert!(!hd_tile(T_TREASURY)); assert!(!hd_tile(T_TREASURY));
} }
} }
+24 -2
View File
@@ -1587,9 +1587,31 @@ impl R3d {
self.lit.set_uniform("LightCount", count); self.lit.set_uniform("LightCount", count);
// wrapped so the shader keeps its precision (the flow speeds are whole tiles per period) // wrapped so the shader keeps its precision (the flow speeds are whole tiles per period)
self.lit.set_uniform("Time", (time % 1000.0) as f32); self.lit.set_uniform("Time", (time % 1000.0) as f32);
// high-res: the 128x128 tiles where a tile gets nearly that many pixels, else the 64x64
// ones (same layout: the chunks keep their uvs, only the texture changes)
let hd_tiles = self.chunks_hd; let hd_tiles = self.chunks_hd;
self.lit.set_uniform("AtlasSize", if hd_tiles { vec2(crate::gfx::ATLAS_HD_W, crate::gfx::ATLAS_HD_H) } else { vec2(ATLAS_W, ATLAS_H) }); let rows = pix.map_or(screen_height() * screen_dpi_scale(), |(_, th, _)| th as f32);
self.lit.set_uniform("CellSize", if hd_tiles { crate::gfx::HD_TILE as f32 } else { 16.0 }); let tile = if !hd_tiles {
16
} else if rows / cam.view_h().max(0.1) >= crate::gfx::HD_TILE2 as f32 * 0.85 {
crate::gfx::HD_TILE2
} else {
crate::gfx::HD_TILE
};
let atlas = match tile {
16 => &gfx.atlas,
crate::gfx::HD_TILE => &gfx.atlas_hd,
_ => &gfx.atlas_hd2,
};
for ch in self.chunks.iter_mut() {
for mesh in [&mut ch.mesh, &mut ch.overlay].into_iter().flatten() {
if mesh.texture.as_ref() != Some(atlas) {
mesh.texture = Some(atlas.clone());
}
}
}
self.lit.set_uniform("AtlasSize", vec2((crate::gfx::ATLAS_COLS * tile) as f32, (crate::gfx::ATLAS_ROWS * tile) as f32));
self.lit.set_uniform("CellSize", tile as f32);
self.lit.set_uniform("HdLiquid", if hd_tiles { 1.0f32 } else { 0.0 }); self.lit.set_uniform("HdLiquid", if hd_tiles { 1.0f32 } else { 0.0 });
gl_use_material(&self.lit); gl_use_material(&self.lit);
for cz in 0..self.ncz { for cz in 0..self.ncz {