Detailed 32 px imp sprite and finer world pixel rows (540, 1080)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
10af85fd95
commit
928c34e78b
7 files changed
+121
-18
No files matched your search
+99
-10
@@ -1368,6 +1368,16 @@ fn gen_atlas() -> Image {
|
||||
|
||||
const SPR: usize = 10;
|
||||
const SPR_CELL: u32 = 12;
|
||||
// the atlas holds every sprite pixel as SPR_K x SPR_K texels, so detailed sprites (HD_SPRITES)
|
||||
// fit into the same cells: a cell is SPR_CELL * SPR_K = 36 texels
|
||||
const SPR_K: u32 = 3;
|
||||
pub const ACELL: u32 = SPR_CELL * SPR_K;
|
||||
|
||||
// Detailed 32x32 sprites (generated, see art/generated/), used instead of the 10x10 ones when
|
||||
// the world is rendered fine enough (render3d::billboards). Five views side by side like VIEWS,
|
||||
// transparent background, the feet on the bottom row. No gear yet: every tier looks the same.
|
||||
const HD_SPRITES: [(EK, &[u8]); 1] = [(EK::Worker, include_bytes!("../assets/sprites/imp-32.png"))];
|
||||
const HD: u32 = 32;
|
||||
|
||||
fn sprite_rows(k: EK) -> [&'static str; 10] {
|
||||
match k {
|
||||
@@ -1825,8 +1835,8 @@ pub const SPRITE_KINDS: [EK; 31] = [
|
||||
EK::Inquisitor,
|
||||
];
|
||||
// width of the sprite texture in pixels
|
||||
pub const SPRITE_TEX_W: f32 = (SPRITE_KINDS.len() * 12) as f32;
|
||||
pub const SPRITE_TEX_H: f32 = (2 * VIEWS * GEAR_TIERS * SPR_CELL) as f32;
|
||||
pub const SPRITE_TEX_W: f32 = ((SPRITE_KINDS.len() + HD_SPRITES.len()) as u32 * ACELL) as f32;
|
||||
pub const SPRITE_TEX_H: f32 = (2 * VIEWS * GEAR_TIERS * ACELL) as f32;
|
||||
|
||||
pub fn sprite_index(k: EK) -> u32 {
|
||||
SPRITE_KINDS.iter().position(|&s| s == k).unwrap_or(0) as u32
|
||||
@@ -2022,10 +2032,14 @@ fn sprite_with_gear(k: EK, tier: u32, view: u32) -> [[Option<Color>; 12]; 12] {
|
||||
}
|
||||
|
||||
// row (view * GEAR_TIERS + tier) * 2: sprites with the gear of the tier, the row after it:
|
||||
// white silhouettes (hit flash)
|
||||
// white silhouettes (hit flash). Columns: SPRITE_KINDS, then HD_SPRITES.
|
||||
fn gen_sprites() -> Image {
|
||||
let n = SPRITE_KINDS.len() as u32;
|
||||
let mut img = Image::gen_image_color((n * SPR_CELL) as u16, (2 * VIEWS * GEAR_TIERS * SPR_CELL) as u16, Color::new(0.0, 0.0, 0.0, 0.0));
|
||||
let mut img = Image::gen_image_color(SPRITE_TEX_W as u16, SPRITE_TEX_H as u16, Color::new(0.0, 0.0, 0.0, 0.0));
|
||||
let put = |img: &mut Image, px: u32, py: u32, col: Color| {
|
||||
img.set_pixel(px, py, col);
|
||||
img.set_pixel(px, py + ACELL, WHITE);
|
||||
};
|
||||
for (i, &k) in SPRITE_KINDS.iter().enumerate() {
|
||||
for view in 0..VIEWS {
|
||||
for tier in 0..GEAR_TIERS {
|
||||
@@ -2033,9 +2047,27 @@ fn gen_sprites() -> Image {
|
||||
for (y, row) in g.iter().enumerate() {
|
||||
for (x, px) in row.iter().enumerate() {
|
||||
if let Some(col) = px {
|
||||
let (px, py) = (i as u32 * SPR_CELL + x as u32, (view * GEAR_TIERS + tier) * 2 * SPR_CELL + y as u32);
|
||||
img.set_pixel(px, py, *col);
|
||||
img.set_pixel(px, py + SPR_CELL, WHITE);
|
||||
let (px, py) = (i as u32 * ACELL + x as u32 * SPR_K, (view * GEAR_TIERS + tier) * 2 * ACELL + y as u32 * SPR_K);
|
||||
for d in 0..SPR_K * SPR_K {
|
||||
put(&mut img, px + d % SPR_K, py + d / SPR_K, *col);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (j, (_, png)) in HD_SPRITES.iter().enumerate() {
|
||||
let src = Image::from_file_with_format(png, Some(ImageFormat::Png)).expect("hd sprite");
|
||||
for view in 0..VIEWS {
|
||||
for tier in 0..GEAR_TIERS {
|
||||
for y in 0..HD.min(src.height as u32) {
|
||||
for x in 0..HD {
|
||||
let col = src.get_pixel(view * HD + x, y);
|
||||
if col.a > 0.5 {
|
||||
// the feet on the 10x10 sprite's bottom row (cell texel 33)
|
||||
let (px, py) = ((n + j as u32) * ACELL + 2 + x, (view * GEAR_TIERS + tier) * 2 * ACELL + 1 + y);
|
||||
put(&mut img, px, py, Color::new(col.r, col.g, col.b, 1.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2175,7 +2207,8 @@ impl Gfx {
|
||||
// the plain 10x10 sprite (interface icons)
|
||||
pub fn sprite_rect(k: EK, white: bool) -> Rect {
|
||||
let i = sprite_index(k);
|
||||
Rect::new((i * SPR_CELL + 1) as f32, (1 + if white { SPR_CELL } else { 0 }) as f32, SPR as f32, SPR as f32)
|
||||
let k = SPR_K as f32;
|
||||
Rect::new((i * ACELL) as f32 + k, (if white { ACELL } else { 0 }) as f32 + k, SPR as f32 * k, SPR as f32 * k)
|
||||
}
|
||||
|
||||
// the whole 12x12 cell with the gear of a level (billboards)
|
||||
@@ -2187,7 +2220,14 @@ impl Gfx {
|
||||
pub fn sprite_cell_view(k: EK, white: bool, level: u32, view: u32) -> Rect {
|
||||
let i = sprite_index(k);
|
||||
let row = (view.min(VIEWS - 1) * GEAR_TIERS + gear_tier(level)) * 2 + white as u32;
|
||||
Rect::new((i * SPR_CELL) as f32, (row * SPR_CELL) as f32, SPR_CELL as f32, SPR_CELL as f32)
|
||||
Rect::new((i * ACELL) as f32, (row * ACELL) as f32, ACELL as f32, ACELL as f32)
|
||||
}
|
||||
|
||||
// the cell of a detailed sprite's view, if the kind has one (same layout as sprite_cell_view)
|
||||
pub fn hd_cell_view(k: EK, white: bool, level: u32, view: u32) -> Option<Rect> {
|
||||
let j = HD_SPRITES.iter().position(|(h, _)| *h == k)? as u32;
|
||||
let row = (view.min(VIEWS - 1) * GEAR_TIERS + gear_tier(level)) * 2 + white as u32;
|
||||
Some(Rect::new(((SPRITE_KINDS.len() as u32 + j) * ACELL) as f32, (row * ACELL) as f32, ACELL as f32, ACELL as f32))
|
||||
}
|
||||
|
||||
pub fn tile_tex(kind: Kind, owner: Owner, var: u8, reinforced: bool) -> u32 {
|
||||
@@ -2410,6 +2450,55 @@ impl Gfx {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod sprite_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn detailed_sprites_in_the_atlas() {
|
||||
let img = gen_sprites();
|
||||
assert_eq!((img.width as f32, img.height as f32), (SPRITE_TEX_W, SPRITE_TEX_H));
|
||||
for view in 0..VIEWS {
|
||||
let r = Gfx::hd_cell_view(EK::Worker, false, 1, view).expect("the imp has a detailed sprite");
|
||||
let w = Gfx::hd_cell_view(EK::Worker, true, 1, view).unwrap();
|
||||
let opaque = |r: Rect| (0..ACELL * ACELL).filter(|i| img.get_pixel(r.x as u32 + i % ACELL, r.y as u32 + i / ACELL).a > 0.5).count();
|
||||
assert!(opaque(r) > 150, "view {view} drawn");
|
||||
assert_eq!(opaque(r), opaque(w), "white silhouette matches");
|
||||
}
|
||||
assert!(Gfx::hd_cell_view(EK::Knight, false, 1, 0).is_none());
|
||||
// the 10x10 sprites: every pixel a 3x3 block
|
||||
let r = Gfx::sprite_cell_view(EK::Knight, false, 1, 0);
|
||||
for y in 0..SPR_CELL {
|
||||
for x in 0..SPR_CELL {
|
||||
let p = img.get_pixel(r.x as u32 + x * SPR_K, r.y as u32 + y * SPR_K);
|
||||
let q = img.get_pixel(r.x as u32 + x * SPR_K + 2, r.y as u32 + y * SPR_K + 2);
|
||||
assert_eq!((p.r, p.g, p.b, p.a), (q.r, q.g, q.b, q.a));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `cargo test --bins dump_imp_cells -- --ignored` writes the imp's cells to /tmp for a look
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn dump_imp_cells() {
|
||||
let img = gen_sprites();
|
||||
let mut out = Image::gen_image_color((ACELL * VIEWS * 4) as u16, (ACELL * 8) as u16, Color::new(0.35, 0.33, 0.38, 1.0));
|
||||
for view in 0..VIEWS {
|
||||
for (row, r) in [Gfx::sprite_cell_view(EK::Worker, false, 1, view), Gfx::hd_cell_view(EK::Worker, false, 1, view).unwrap()].iter().enumerate() {
|
||||
for y in 0..ACELL * 4 {
|
||||
for x in 0..ACELL * 4 {
|
||||
let p = img.get_pixel(r.x as u32 + x / 4, r.y as u32 + y / 4);
|
||||
if p.a > 0.5 {
|
||||
out.set_pixel(view * ACELL * 4 + x, (ACELL * 8 - 1) - (row as u32 * ACELL * 4 + y), p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out.export_png("/tmp/imp-cells.png");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod doc_images {
|
||||
use super::*;
|
||||
@@ -2438,7 +2527,7 @@ mod doc_images {
|
||||
for &k in SPRITE_KINDS.iter() {
|
||||
let id = crate::game::st(k).name.trim_start_matches("unit.");
|
||||
for (lv, tag) in [(1u32, "1"), (5, "5"), (10, "10")] {
|
||||
save(&sprites, Gfx::sprite_cell(k, false, lv), 8, &format!("docs/img/units/{}_{}.png", id, tag));
|
||||
save(&sprites, Gfx::sprite_cell(k, false, lv), 3, &format!("docs/img/units/{}_{}.png", id, tag));
|
||||
}
|
||||
}
|
||||
let atlas = gen_atlas();
|
||||
|
||||
Reference in new issue
Block a user