High-res tile textures: floor, claimed floor, earth, rock, gold, churning water and lava
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No files matched your search
@@ -5,3 +5,4 @@ www/dungeoncraft.wasm
|
|||||||
www/mq_js_bundle.js
|
www/mq_js_bundle.js
|
||||||
play/
|
play/
|
||||||
dist/
|
dist/
|
||||||
|
art/generated/creature-previews/
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# High-res tile textures (test)
|
||||||
|
|
||||||
|
Generated on 2026-11-07 with fal.ai for the high-res graphics style (`HD_TILES` in `src/gfx.rs`).
|
||||||
|
|
||||||
|
- Model: `fal-ai/flux-2-pro`, `image_size: square_hd` (1024x1024), `output_format: png`,
|
||||||
|
`seed: 4242` (floor and gold: a second try with `seed: 4243` and the prompts below; the first
|
||||||
|
floor had a frame and a centre motif, the first gold too few veins)
|
||||||
|
- Prompt: the kind's sentence followed by the shared style text.
|
||||||
|
- `tools/make_tile.py <name>-raw.png <name> --size 128 --colors 32` scales it to a 2x2 block of
|
||||||
|
64x64 tiles, makes it seamless and reduces the colours; `<name>-128.png` is copied to
|
||||||
|
`assets/textures/<name>.png`. `<name>-128-preview.png`: 3x3 copies to check the seams.
|
||||||
|
|
||||||
|
Shared style text:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Seamless tileable square texture, viewed straight from above (orthographic top-down, no perspective), flat even lighting, no shadows, no vignette, no border, no text. Hand-painted pixel art style for a dark-fantasy dungeon strategy game: chunky clear shapes, crisp edges, limited muted palette, readable at small size. The pattern continues seamlessly across all four edges.
|
||||||
|
```
|
||||||
|
|
||||||
|
## floor: T_FLOOR (dug-out floor)
|
||||||
|
|
||||||
|
```text
|
||||||
|
A uniform all-over pattern of rough grey-brown cobblestones of mixed sizes set in packed dirt, evenly spread from edge to edge with no frame, no border and no centre motif, like a cropped piece of a large dungeon floor.
|
||||||
|
```
|
||||||
|
|
||||||
|
## claimed: T_CLAIM (claimed floor)
|
||||||
|
|
||||||
|
```text
|
||||||
|
The floor of an evil dungeon master's lair: fitted dark crimson-purple flagstones with thin black grout lines, some stones carry a small faint carved demonic rune, worn and polished.
|
||||||
|
```
|
||||||
|
|
||||||
|
## earth: T_EARTH (earth blocks)
|
||||||
|
|
||||||
|
```text
|
||||||
|
Solid packed brown earth seen from above, soil with small stones, roots and darker clumps, slightly lumpy.
|
||||||
|
```
|
||||||
|
|
||||||
|
## rock: T_ROCK (bedrock)
|
||||||
|
|
||||||
|
```text
|
||||||
|
Hard dark blue-grey bedrock seen from above, angular rock facets and deep cracks, very solid and impenetrable.
|
||||||
|
```
|
||||||
|
|
||||||
|
## gold: T_GOLD (gold seam)
|
||||||
|
|
||||||
|
```text
|
||||||
|
A uniform all-over pattern of brown earth crossed by many thick glittering veins of bright yellow gold ore with large shining gold nuggets, the gold covering about a third of the surface, evenly spread from edge to edge.
|
||||||
|
```
|
||||||
|
|
||||||
|
## water: T_WATER
|
||||||
|
|
||||||
|
```text
|
||||||
|
Deep dark blue underground river water seen from above, gentle wave crests and ripples in lighter blue-cyan, slight foam streaks.
|
||||||
|
```
|
||||||
|
|
||||||
|
## lava: T_LAVA
|
||||||
|
|
||||||
|
```text
|
||||||
|
Molten lava seen from above, glowing orange and yellow molten rock flowing in swirls, darker red cooling streaks, very hot and bright, no black crust.
|
||||||
|
```
|
||||||
|
|
||||||
|
## In the game
|
||||||
|
|
||||||
|
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
|
||||||
|
sideways over it, so the surface churns.
|
||||||
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 25 KiB |
@@ -99,6 +99,7 @@ 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 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
|
||||||
@@ -325,6 +326,55 @@ fn earth_px(x: i32, y: i32, v: u32) -> Color {
|
|||||||
shade(base, (big + fine) as i32)
|
shade(base, (big + fine) as i32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- high-res tiles
|
||||||
|
// The high-res style's atlas: the same cells as the 16x16 atlas, HD_TILE x HD_TILE texels each.
|
||||||
|
// Cells without an own texture are the 16x16 ones scaled up. HD_TILES: generated textures (see
|
||||||
|
// art/generated/textures/), each a 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 ATLAS_HD_W: f32 = (ATLAS_COLS * HD_TILE) as f32;
|
||||||
|
pub const ATLAS_HD_H: f32 = (ATLAS_ROWS * HD_TILE) as f32;
|
||||||
|
const HD_TILES: [(u32, &[u8]); 7] = [
|
||||||
|
(T_FLOOR, include_bytes!("../assets/textures/floor.png")),
|
||||||
|
(T_CLAIM, include_bytes!("../assets/textures/claimed.png")),
|
||||||
|
(T_EARTH, include_bytes!("../assets/textures/earth.png")),
|
||||||
|
(T_ROCK, include_bytes!("../assets/textures/rock.png")),
|
||||||
|
(T_GOLD, include_bytes!("../assets/textures/gold.png")),
|
||||||
|
(T_WATER, include_bytes!("../assets/textures/water.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)
|
||||||
|
pub fn hd_tile(id: u32) -> bool {
|
||||||
|
HD_TILES.iter().any(|&(base, _)| id / 4 * 4 == base)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_hd_atlas() -> Image {
|
||||||
|
let small = gen_atlas();
|
||||||
|
let k = HD_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));
|
||||||
|
for y in 0..ATLAS_HD_H as u32 {
|
||||||
|
for x in 0..ATLAS_HD_W as u32 {
|
||||||
|
img.set_pixel(x, y, small.get_pixel(x / k, y / k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for &(base, png) in HD_TILES.iter() {
|
||||||
|
let src = Image::from_file_with_format(png, Some(ImageFormat::Png)).expect("hd tile");
|
||||||
|
for v in 0..4 {
|
||||||
|
let (sx, sy) = ((v % 2) * HD_TILE, (v / 2) * HD_TILE);
|
||||||
|
let id = base + v;
|
||||||
|
let (ox, oy) = ((id % ATLAS_COLS) * HD_TILE, (id / ATLAS_COLS) * HD_TILE);
|
||||||
|
for y in 0..HD_TILE {
|
||||||
|
for x in 0..HD_TILE {
|
||||||
|
img.set_pixel(ox + x, oy + y, src.get_pixel(sx + x, sy + y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img
|
||||||
|
}
|
||||||
|
|
||||||
fn gen_atlas() -> Image {
|
fn gen_atlas() -> Image {
|
||||||
let mut cv = Canvas { img: Image::gen_image_color((ATLAS_COLS * 16) as u16, (ATLAS_ROWS * 16) as u16, Color::new(0.0, 0.0, 0.0, 0.0)) };
|
let mut cv = Canvas { img: Image::gen_image_color((ATLAS_COLS * 16) as u16, (ATLAS_ROWS * 16) as u16, Color::new(0.0, 0.0, 0.0, 0.0)) };
|
||||||
for v in 0..4u32 {
|
for v in 0..4u32 {
|
||||||
@@ -2360,9 +2410,11 @@ 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());
|
||||||
|
atlas_hd.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, cursors, badges, font, font_map }
|
Gfx { atlas, sprites, hd_sprites, atlas_hd, cursors, badges, font, font_map }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cell(id: u32) -> Rect {
|
pub fn cell(id: u32) -> Rect {
|
||||||
@@ -2632,6 +2684,39 @@ impl Gfx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod hd_atlas_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn high_res_atlas() {
|
||||||
|
let small = gen_atlas();
|
||||||
|
let hd = gen_hd_atlas();
|
||||||
|
assert_eq!((hd.width as f32, hd.height as f32), (ATLAS_HD_W, ATLAS_HD_H));
|
||||||
|
let px = |img: &Image, x: u32, y: u32| {
|
||||||
|
let p = img.get_pixel(x, y);
|
||||||
|
(p.r, p.g, p.b, p.a)
|
||||||
|
};
|
||||||
|
// a cell without an own texture: the 16x16 one scaled up
|
||||||
|
let (cx, cy) = ((T_TREASURY % ATLAS_COLS) * 16, (T_TREASURY / ATLAS_COLS) * 16);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
// the generated ones: the quarters of their 2x2 sheet
|
||||||
|
for &(base, png) in HD_TILES.iter() {
|
||||||
|
let src = Image::from_file_with_format(png, Some(ImageFormat::Png)).unwrap();
|
||||||
|
assert_eq!((src.width, src.height), (2 * HD_TILE as u16, 2 * HD_TILE as u16));
|
||||||
|
for v in 0..4 {
|
||||||
|
let id = base + v;
|
||||||
|
assert!(hd_tile(id));
|
||||||
|
let (ox, oy) = ((id % ATLAS_COLS) * HD_TILE, (id / ATLAS_COLS) * HD_TILE);
|
||||||
|
assert_eq!(px(&hd, ox + 7, oy + 40), px(&src, (v % 2) * HD_TILE + 7, (v / 2) * HD_TILE + 40));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(!hd_tile(T_TREASURY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod sprite_tests {
|
mod sprite_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -259,6 +259,19 @@ uniform vec4 LightCol[24];
|
|||||||
uniform float LightCount;
|
uniform float LightCount;
|
||||||
uniform highp float Time; // same precision as in the vertex shader (required for linking)
|
uniform highp float Time; // same precision as in the vertex shader (required for linking)
|
||||||
uniform vec2 AtlasSize;
|
uniform vec2 AtlasSize;
|
||||||
|
uniform float CellSize; // texels per atlas cell (16; high-res 64)
|
||||||
|
uniform float HdLiquid; // 1: high-res liquids (2x2 blocks of tiles, a second flowing layer)
|
||||||
|
|
||||||
|
// a flowing liquid's texture at world position p inside the kind's cells (uv names one of them);
|
||||||
|
// high-res: the four variant cells form a 2x2 block that repeats every two tiles
|
||||||
|
vec2 liquid_uv(vec2 p) {
|
||||||
|
vec2 cell = floor(uv * AtlasSize / CellSize);
|
||||||
|
if (HdLiquid > 0.5) {
|
||||||
|
vec2 q = mod(floor(p), 2.0);
|
||||||
|
cell.x = floor(cell.x / 4.0) * 4.0 + q.x + 2.0 * q.y;
|
||||||
|
}
|
||||||
|
return (cell + fract(p)) * CellSize / AtlasSize;
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 tuv = uv;
|
vec2 tuv = uv;
|
||||||
@@ -276,12 +289,22 @@ void main() {
|
|||||||
float ph2 = wpos.z * 1.9 - wpos.x * 0.5 - Time * ts * 1.3;
|
float ph2 = wpos.z * 1.9 - wpos.x * 0.5 - Time * ts * 1.3;
|
||||||
vec2 slope = vec2(cos(ph1) * 1.3 * 0.6 - cos(ph2) * 0.5 * 0.4, cos(ph1) * 0.4 * 0.6 + cos(ph2) * 1.9 * 0.4);
|
vec2 slope = vec2(cos(ph1) * 1.3 * 0.6 - cos(ph2) * 0.5 * 0.4, cos(ph1) * 0.4 * 0.6 + cos(ph2) * 1.9 * 0.4);
|
||||||
vec2 p = wpos.xz - dir * speed * Time + slope * (lava ? 0.05 : 0.07);
|
vec2 p = wpos.xz - dir * speed * Time + slope * (lava ? 0.05 : 0.07);
|
||||||
vec2 cell = floor(uv * AtlasSize / 16.0) * 16.0;
|
tuv = liquid_uv(p);
|
||||||
tuv = (cell + fract(p) * 16.0) / AtlasSize;
|
|
||||||
float ripple = sin(wpos.x * 5.0 + wpos.z * 3.0 + Time * ts * 1.4) * sin(wpos.z * 4.0 - wpos.x * 2.0 + Time * ts);
|
float ripple = sin(wpos.x * 5.0 + wpos.z * 3.0 + Time * ts * 1.4) * sin(wpos.z * 4.0 - wpos.x * 2.0 + Time * ts);
|
||||||
wave = dot(slope, vec2(0.45, 0.35)) * 0.6 + ripple * 0.4;
|
wave = dot(slope, vec2(0.45, 0.35)) * 0.6 + ripple * 0.4;
|
||||||
}
|
}
|
||||||
vec4 tex = texture2D(Texture, tuv);
|
vec4 tex = texture2D(Texture, tuv);
|
||||||
|
if (liquid && HdLiquid > 0.5) {
|
||||||
|
// a second, larger layer drifting slower and sideways: the surface churns instead of
|
||||||
|
// only sliding along
|
||||||
|
bool lava = color.a < 0.5;
|
||||||
|
vec2 dir = wnorm.xz / max(length(wnorm.xz), 0.001);
|
||||||
|
vec2 side = vec2(-dir.y, dir.x);
|
||||||
|
float speed = lava ? 0.07 : 0.15;
|
||||||
|
vec2 p2 = wpos.xz * 0.6 + vec2(0.37, 0.71) - dir * speed * Time + side * sin(Time * (lava ? 0.21 : 0.33)) * 0.3;
|
||||||
|
vec4 tex2 = texture2D(Texture, liquid_uv(p2));
|
||||||
|
tex = vec4(mix(tex.rgb, tex2.rgb, 0.42), 1.0);
|
||||||
|
}
|
||||||
if (tex.a < 0.5) discard;
|
if (tex.a < 0.5) discard;
|
||||||
// see-through units (invisibility): every other pixel left out
|
// see-through units (invisibility): every other pixel left out
|
||||||
if (color.a < 0.37 && mod(floor(gl_FragCoord.x) + floor(gl_FragCoord.y), 2.0) < 1.0) discard;
|
if (color.a < 0.37 && mod(floor(gl_FragCoord.x) + floor(gl_FragCoord.y), 2.0) < 1.0) discard;
|
||||||
@@ -319,6 +342,8 @@ fn lit_material() -> Material {
|
|||||||
UniformDesc::new("LightCount", UniformType::Float1),
|
UniformDesc::new("LightCount", UniformType::Float1),
|
||||||
UniformDesc::new("Time", UniformType::Float1),
|
UniformDesc::new("Time", UniformType::Float1),
|
||||||
UniformDesc::new("AtlasSize", UniformType::Float2),
|
UniformDesc::new("AtlasSize", UniformType::Float2),
|
||||||
|
UniformDesc::new("CellSize", UniformType::Float1),
|
||||||
|
UniformDesc::new("HdLiquid", UniformType::Float1),
|
||||||
],
|
],
|
||||||
textures: vec![],
|
textures: vec![],
|
||||||
},
|
},
|
||||||
@@ -766,7 +791,8 @@ fn block_shell(mk: &mut MB, map: &Map, x: i32, z: i32, n: usize, fill: [u8; 4],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &Texture2D, wall_sub: usize) -> [Option<Mesh>; 4] {
|
// `hd`: the high-res atlas; its generated textures are laid out by the tiles' positions
|
||||||
|
fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &Texture2D, wall_sub: usize, hd: bool) -> [Option<Mesh>; 4] {
|
||||||
let mut mb = MB::new();
|
let mut mb = MB::new();
|
||||||
let ov = MB::new(); // textured overlays (currently unused)
|
let ov = MB::new(); // textured overlays (currently unused)
|
||||||
let mut ln = MB::new();
|
let mut ln = MB::new();
|
||||||
@@ -784,6 +810,9 @@ fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &T
|
|||||||
if t.kind == Kind::Mushroom && t.res & MUSH_GROVE != 0 {
|
if t.kind == Kind::Mushroom && t.res & MUSH_GROVE != 0 {
|
||||||
tex = crate::gfx::T_MOSS + (t.var as u32 & 3);
|
tex = crate::gfx::T_MOSS + (t.var as u32 & 3);
|
||||||
}
|
}
|
||||||
|
if hd && crate::gfx::hd_tile(tex) {
|
||||||
|
tex = tex / 4 * 4 + (x & 1) as u32 + (z & 1) as u32 * 2;
|
||||||
|
}
|
||||||
let (ua, ub) = cell_uv(tex);
|
let (ua, ub) = cell_uv(tex);
|
||||||
let (xf, zf) = (x as f32, z as f32);
|
let (xf, zf) = (x as f32, z as f32);
|
||||||
if !map.shown(idx(x, z)) {
|
if !map.shown(idx(x, z)) {
|
||||||
@@ -1202,6 +1231,7 @@ pub struct R3d {
|
|||||||
pub pixel_rows: u32, // pixel look: the world is rendered this many rows high (0: off)
|
pub pixel_rows: u32, // pixel look: the world is rendered this many rows high (0: off)
|
||||||
target: Option<(RenderTarget, u32, u32)>, // low resolution render target
|
target: Option<(RenderTarget, u32, u32)>, // low resolution render target
|
||||||
lava: Vec<f32>, // lava light per tile (see lava_field)
|
lava: Vec<f32>, // lava light per tile (see lava_field)
|
||||||
|
chunks_hd: bool, // the chunks were built with the high-res atlas
|
||||||
}
|
}
|
||||||
|
|
||||||
// The 3D resolution setting refers to a window 1080 device pixels high: smaller windows use a
|
// The 3D resolution setting refers to a window 1080 device pixels high: smaller windows use a
|
||||||
@@ -1252,7 +1282,7 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32) -> u64 {
|
|||||||
|
|
||||||
impl R3d {
|
impl R3d {
|
||||||
pub fn new() -> R3d {
|
pub fn new() -> R3d {
|
||||||
R3d { lit: lit_material(), glow: glow_material(), opaque: opaque_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, pixel_rows: crate::settings::PIXEL_ART_ROWS as u32, target: None, lava: Vec::new() }
|
R3d { lit: lit_material(), glow: glow_material(), opaque: opaque_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, pixel_rows: crate::settings::PIXEL_ART_ROWS as u32, target: None, lava: Vec::new(), chunks_hd: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changing the wall detail rebuilds all chunks
|
// Changing the wall detail rebuilds all chunks
|
||||||
@@ -1292,6 +1322,14 @@ impl R3d {
|
|||||||
ch.sig = 0;
|
ch.sig = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// the graphics style changed: the chunks are built again with the other atlas
|
||||||
|
let hd = self.pixel_rows >= crate::settings::HIGH_RES_ROWS as u32;
|
||||||
|
if hd != self.chunks_hd {
|
||||||
|
self.chunks_hd = hd;
|
||||||
|
for ch in self.chunks.iter_mut() {
|
||||||
|
ch.sig = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
if view_side() != side {
|
if view_side() != side {
|
||||||
// another master looks: the marks change colour
|
// another master looks: the marks change colour
|
||||||
VIEW_SIDE.with(|v| v.set(side));
|
VIEW_SIDE.with(|v| v.set(side));
|
||||||
@@ -1316,7 +1354,8 @@ impl R3d {
|
|||||||
lava_fresh = true;
|
lava_fresh = true;
|
||||||
}
|
}
|
||||||
let ch = &mut self.chunks[(cz * self.ncx + cx) as usize];
|
let ch = &mut self.chunks[(cz * self.ncx + cx) as usize];
|
||||||
let [m, o, l, k] = build_chunk(&g.map, a, &self.lava, cx, cz, &gfx.atlas, self.wall_sub);
|
let atlas = if self.chunks_hd { &gfx.atlas_hd } else { &gfx.atlas };
|
||||||
|
let [m, o, l, k] = build_chunk(&g.map, a, &self.lava, cx, cz, atlas, self.wall_sub, self.chunks_hd);
|
||||||
ch.mesh = m;
|
ch.mesh = m;
|
||||||
ch.overlay = o;
|
ch.overlay = o;
|
||||||
ch.lines = l;
|
ch.lines = l;
|
||||||
@@ -1548,7 +1587,10 @@ 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);
|
||||||
self.lit.set_uniform("AtlasSize", vec2(ATLAS_W, ATLAS_H));
|
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) });
|
||||||
|
self.lit.set_uniform("CellSize", if hd_tiles { crate::gfx::HD_TILE as f32 } else { 16.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 {
|
||||||
for cx in 0..self.ncx {
|
for cx in 0..self.ncx {
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Turn a generated texture into a seamless pixel-art tile sheet for the high-res atlas.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
make_tile.py raw.png out_prefix [--size 128] [--colors 32] [--band 14]
|
||||||
|
|
||||||
|
The picture is scaled down to size x size (default 128: a 2x2 block of 64x64 tiles; the game
|
||||||
|
lays the four quarters out by the tiles' positions, so the floor repeats only every two tiles),
|
||||||
|
made seamless (near the edges it fades into a copy shifted by half the size, whose edges wrap
|
||||||
|
around without a seam; `--band` pixels wide) and reduced to a palette without dithering.
|
||||||
|
|
||||||
|
Writes out_prefix-<size>.png and out_prefix-<size>-preview.png (3x3 copies scaled up 3x, to
|
||||||
|
check the seams). Needs Pillow.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from PIL import Image, ImageChops
|
||||||
|
|
||||||
|
|
||||||
|
def seamless(img, band):
|
||||||
|
n = img.width
|
||||||
|
shifted = ImageChops.offset(img, n // 2, n // 2)
|
||||||
|
mask = Image.new("L", (n, n))
|
||||||
|
for y in range(n):
|
||||||
|
for x in range(n):
|
||||||
|
# 0 at the edges (the shifted copy, continuous across the wrap), 255 inside
|
||||||
|
d = min(x, n - 1 - x, y, n - 1 - y)
|
||||||
|
mask.putpixel((x, y), int(255 * min(1.0, d / band)))
|
||||||
|
return Image.composite(img, shifted, mask)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("raw")
|
||||||
|
ap.add_argument("out")
|
||||||
|
ap.add_argument("--size", type=int, default=128)
|
||||||
|
ap.add_argument("--colors", type=int, default=32)
|
||||||
|
ap.add_argument("--band", type=int, default=14)
|
||||||
|
a = ap.parse_args()
|
||||||
|
|
||||||
|
img = Image.open(a.raw).convert("RGB")
|
||||||
|
s = min(img.size)
|
||||||
|
img = img.crop(((img.width - s) // 2, (img.height - s) // 2, (img.width + s) // 2, (img.height + s) // 2))
|
||||||
|
img = img.resize((a.size, a.size), Image.LANCZOS)
|
||||||
|
img = seamless(img, a.band)
|
||||||
|
img = img.quantize(colors=a.colors, method=Image.Quantize.MEDIANCUT, dither=Image.Dither.NONE).convert("RGB")
|
||||||
|
img.save(f"{a.out}-{a.size}.png")
|
||||||
|
prev = Image.new("RGB", (a.size * 3, a.size * 3))
|
||||||
|
for k in range(9):
|
||||||
|
prev.paste(img, ((k % 3) * a.size, (k // 3) * a.size))
|
||||||
|
prev.resize((prev.width * 2, prev.height * 2), Image.NEAREST).save(f"{a.out}-{a.size}-preview.png")
|
||||||
|
print(f"{a.out}-{a.size}.png")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||