use glam::IVec2; use serde::Deserialize; use std::{collections::HashMap, error::Error, fs::File, io::BufReader, path::Path}; fn load_img(path: &Path) -> Result<(Vec, u32, u32), Box> { let mut decoder = png::Decoder::new(BufReader::new(File::open(path)?)); decoder.set_transformations(png::Transformations::IDENTITY); let mut reader = decoder.read_info()?; // only None if it's too big for memory let mut buf = vec![0; reader.output_buffer_size().unwrap()]; let info = reader.next_frame(&mut buf)?; if info.color_type != png::ColorType::Indexed { return Err(format!("{:#?}: not indexed", path.to_str()).into()); } if info.bit_depth != png::BitDepth::Eight { return Err(format!( "{:#?}: expected 8-bit, got {:?}", path.to_str(), info.bit_depth ) .into()); } buf.truncate(info.buffer_size()); Ok((buf, info.width, info.height)) } #[derive(Deserialize, Clone, Copy, Debug)] pub struct TileDimensions { pub short: u32, pub long: u32, } #[derive(Deserialize, Clone, Copy, Debug)] pub enum TilePixelType { Void, Terrain(f32), Custom(u8), } #[derive(Deserialize, Debug)] pub struct PaletteEntry { pub solidity: f32, } #[derive(Deserialize, Debug)] pub struct TilesetManifest { pub dimensions: TileDimensions, pub palette: HashMap, } fn load_manifest(path: &Path) -> Result> { Ok(toml::from_str(&std::fs::read_to_string(path)?)?) } #[derive(Debug)] pub struct Tile { pub orientation: TileOrientation, pub dimensions: TileDimensions, pub pixels: Vec, } impl Tile { pub fn size(&self) -> IVec2 { let short = self.dimensions.short as i32; let long = self.dimensions.long as i32; match self.orientation { TileOrientation::Horizontal => IVec2::new(long, short), TileOrientation::Vertical => IVec2::new(short, long), } } #[inline] pub fn pixel_at(&self, position: IVec2) -> TilePixelType { self.pixels[(position.x + position.y * self.size().x) as usize] } } #[derive(Debug)] pub struct Tileset { pub manifest: TilesetManifest, pub vertical_tiles: Vec, pub horizontal_tiles: Vec, } #[derive(Debug, Clone, Copy)] pub enum TileOrientation { Horizontal, Vertical, } fn parse_row( y: u32, img: &(Vec, u32, u32), manifest: &TilesetManifest, orientation: TileOrientation, ) -> Result, String> { let ey = match orientation { TileOrientation::Horizontal => y + manifest.dimensions.short, TileOrientation::Vertical => y + manifest.dimensions.long, }; let mut tiles = Vec::new(); let mut x = 1; loop { if x >= img.1 { break; } let left_border = img.0[(x - 1 + y * img.1) as usize]; if left_border != 9 { break; } let mut tile_pixels = Vec::new(); let ex = match orientation { TileOrientation::Horizontal => x + manifest.dimensions.long, TileOrientation::Vertical => x + manifest.dimensions.short, }; for py in y..ey { for px in x..ex { let idx = img.0[(px + py * img.1) as usize]; let pixel_type = match idx { 0 => TilePixelType::Void, 1 => TilePixelType::Terrain(0.125), 2 => TilePixelType::Terrain(0.25), 3 => TilePixelType::Terrain(0.375), 4 => TilePixelType::Terrain(0.5), 5 => TilePixelType::Terrain(0.625), 6 => TilePixelType::Terrain(0.75), 7 => TilePixelType::Terrain(0.875), 8 => TilePixelType::Terrain(1.0), // 9 = border 9 => unreachable!(), _ => TilePixelType::Custom(idx), }; tile_pixels.push(pixel_type); } } debug_assert!( tile_pixels.len() == (manifest.dimensions.long * manifest.dimensions.short) as usize ); tiles.push(Tile { orientation, dimensions: manifest.dimensions, pixels: tile_pixels, }); x = ex + 3; } Ok(tiles) } fn parse_tiles( img: &(Vec, u32, u32), manifest: &TilesetManifest, ) -> Result<(Vec, Vec), String> { let mut horizontal_tiles = Vec::new(); let mut vertical_tiles = Vec::new(); let mut y = 0; loop { if y >= img.2 { break; } let edge = img.0[((y) * img.1) as usize] == 9; if !edge { y += 1; continue; } let orientation = if img.0[((y + manifest.dimensions.short + 2) * img.1) as usize] == 0 { TileOrientation::Horizontal } else { TileOrientation::Vertical }; let parse_result = parse_row(y + 1, img, manifest, orientation); match parse_result { Ok(mut tiles) => match orientation { TileOrientation::Horizontal => { println!("Parsed horizontal row {y}. {n} entries", n = tiles.len()); horizontal_tiles.append(&mut tiles); y += manifest.dimensions.short + 3; } TileOrientation::Vertical => { println!("Parsed vertical row {y}. {n} entries", n = tiles.len()); vertical_tiles.append(&mut tiles); y += manifest.dimensions.long + 3; } }, Err(e) => { return Err(format!( "Failed to parse row {} ({:#?}): {e}", y + 1, orientation )); } } } Ok((horizontal_tiles, vertical_tiles)) } pub fn load_tileset(path: &str) -> Tileset { let img_result = load_img(&Path::join(Path::new(path), Path::new("img.png"))); match img_result { Ok(img_result) => { let manifest_result = load_manifest(&Path::join(Path::new(path), Path::new("manifest.toml"))); match manifest_result { Ok(manifest) => { debug_assert!(manifest.dimensions.long == manifest.dimensions.short * 2); let (horizontal_tiles, vertical_tiles) = parse_tiles(&img_result, &manifest).unwrap(); Tileset { manifest, vertical_tiles, horizontal_tiles, } } Err(e) => { panic!("Couldn't load sprite: {}", e) } } } Err(e) => { panic!("Couldn't load sprite: {}", e) } } }