summaryrefslogtreecommitdiff
path: root/src/proc_gen/tileset_loader.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/proc_gen/tileset_loader.rs')
-rw-r--r--src/proc_gen/tileset_loader.rs204
1 files changed, 204 insertions, 0 deletions
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<u8>, u32, u32), Box<dyn Error>> {
+ 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<u8, TilesetPixelType>,
+}
+
+fn load_manifest(path: &Path) -> Result<TilesetManifest, Box<dyn Error>> {
+ Ok(toml::from_str(&std::fs::read_to_string(path)?)?)
+}
+
+#[derive(Debug)]
+pub struct Tile {
+ pub pixels: Vec<TilesetPixelType>,
+}
+
+#[derive(Debug)]
+pub struct Tileset {
+ pub dimensions: TilesetDimensions,
+ pub vertical_tiles: Vec<Tile>,
+ pub horizontal_tiles: Vec<Tile>,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum TileOrientation {
+ Horizontal,
+ Vertical,
+}
+
+fn parse_row(
+ y: u32,
+ img: &(Vec<u8>, u32, u32),
+ manifest: &TilesetManifest,
+ orientation: TileOrientation,
+) -> Result<Vec<Tile>, 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<u8>, u32, u32),
+ manifest: &TilesetManifest,
+) -> Result<(Vec<Tile>, Vec<Tile>), 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)
+ }
+ }
+}