From 56bba5560be95c1e05bfec11e4fc085654f3589d Mon Sep 17 00:00:00 2001 From: Kai Stevenson Date: Sun, 30 Aug 2026 18:42:16 -0700 Subject: working with tilesets --- src/proc_gen/tileset_loader.rs | 204 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 src/proc_gen/tileset_loader.rs (limited to 'src/proc_gen/tileset_loader.rs') diff --git a/src/proc_gen/tileset_loader.rs b/src/proc_gen/tileset_loader.rs new file mode 100644 index 0000000..925b3ac --- /dev/null +++ b/src/proc_gen/tileset_loader.rs @@ -0,0 +1,204 @@ +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, Debug)] +pub struct TilesetDimensions { + pub short: u32, + pub long: u32, +} + +#[derive(Deserialize, Clone, Copy, Debug)] +pub enum TilesetPixelType { + Void = 0, + Terrain, +} + +#[derive(Deserialize, Debug)] +struct TilesetManifest { + dimensions: TilesetDimensions, + palette: HashMap, +} + +fn load_manifest(path: &Path) -> Result> { + Ok(toml::from_str(&std::fs::read_to_string(path)?)?) +} + +#[derive(Debug)] +pub struct Tile { + pub pixels: Vec, +} + +#[derive(Debug)] +pub struct Tileset { + pub dimensions: TilesetDimensions, + 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]; + // 2 = border color + if left_border != 2 { + 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 palette = manifest.palette.get(&idx); + + match palette { + Some(pixel_type) => tile_pixels.push(*pixel_type), + None => { + return Err(format!( + "Pixel ({px},{py}) resolved to idx {idx} which isn't in the palette" + )); + } + } + } + } + + debug_assert!( + tile_pixels.len() == (manifest.dimensions.long * manifest.dimensions.short) as usize + ); + + tiles.push(Tile { + 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] == 2; + 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 { + dimensions: manifest.dimensions, + vertical_tiles, + horizontal_tiles, + } + } + Err(e) => { + panic!("Couldn't load sprite: {}", e) + } + } + } + Err(e) => { + panic!("Couldn't load sprite: {}", e) + } + } +} -- cgit v1.3.1