diff --git a/src/reskit/level/dmapack.rs b/src/reskit/level/dmapack.rs index 63dbb8f..bff4f96 100644 --- a/src/reskit/level/dmapack.rs +++ b/src/reskit/level/dmapack.rs @@ -1,5 +1,6 @@ -use std::{error::Error, collections::{HashSet, HashMap}}; -use super::converter::Metatile; +use std::{error::Error, collections::HashMap, cmp::max}; +use crate::reskit::level::system::get_tilemap_prefix_palette; +use super::converter::{Metatile, TiledTilemap, TiledTileset}; #[derive(Debug, PartialEq)] pub struct PackSettings { @@ -7,13 +8,92 @@ pub struct PackSettings { pub slot: [u16; 2] } -pub type Bucket = Vec; +pub type Bucket<'a> = Vec>; -pub fn get_buckets( metatiles: &Vec ) -> Result, Box> { - let mut buckets: HashMap = HashMap::new(); +pub fn get_buckets( tilemap: &TiledTilemap ) -> Result, Box> { + let mut buckets: HashMap = tilemap.metatiles.iter().map( | metatile | ( metatile.pack.bucket, Bucket::new() ) ).collect(); // Prepare buckets. Each bucket is exactly 64 tiles wide, and as tall as the largest // height in the bucket. + for ( bucket_id, bucket ) in &mut buckets { + let height = get_bucket_height( *bucket_id, &tilemap.metatiles ); + bucket.resize_with( 64 * height, || None ); + } - todo!() + // Build definitions and stamp them into their respective buckets and bucket slots + for metatile in &tilemap.metatiles { + // Get definition (in Vec form) so we can stamp it into the bucket + let definition = get_metatile_definition( metatile, &tilemap.tileset )?; + + // Get bucket to stamp definition into + let bucket: &mut Bucket = buckets.get_mut( &metatile.pack.bucket ).ok_or( "internal error: no dmapack bucket" )?; + + // Stamp the definition into the appropriate slot of the bucket. + for y in 0..metatile.height { + for x in 0..metatile.width { + let metatile_tile = definition.get( ( ( y * metatile.width ) + x ) as usize ).ok_or( "internal error: metatile tile expected" )?; + + let bucket_target_x = x + metatile.pack.slot[ 0 ]; + let bucket_target_y = y + metatile.pack.slot[ 1 ]; + + let bucket_target_tile = bucket.get_mut( ( ( bucket_target_y * 64 ) + bucket_target_x ) as usize ).ok_or( "internal error: bucket not correct size" )?; + + // At any point if we encounter an overlapping bucket segment, throw an error. + if let Some( _ ) = bucket_target_tile { + return Err( format!( "invalid file: overlapping bucket segment at {},{}", metatile.pack.slot[ 0 ], metatile.pack.slot[ 1 ] ) )?; + } + + // Stamp the segment + bucket_target_tile.replace( *metatile_tile ); + } + } + } + + let mut sorted_buckets: Vec<(u16, Bucket)> = buckets.into_iter().map( | pair | pair ).collect(); + sorted_buckets.sort_by_key( | ( bucket_id, _ ) | *bucket_id ); + + Ok( sorted_buckets ) +} + +/** + * Get the metatile definition (the block of u16s in sega genesis vdp nametable format) + */ +fn get_metatile_definition( metatile: &Metatile, tileset: &Vec ) -> Result, Box> { + let mut result: Vec = Vec::new(); + + let ( tile_id_prefix, palettes ) = get_tilemap_prefix_palette( &metatile.source, &tileset )?; + for tile in &metatile.tiles { + let palette = { + if *tile == 0 { + 0 + } else { + let palette = palettes.get( *tile as usize ).ok_or( "internal error: no correlation between tile index and palette index" )?; + if let Ok( palette ) = palette.ok_or( "" ) { + palette + } else { + println!( "{:?}", palettes ); + return Err( format!( "invalid file: tile \"{}\" in metatile tilemap does not name reskit-palette attribute", tile ) )?; + } + } + }; + + let tile_id: u16 = if *tile == 0 { 0 } else { ( tile + tile_id_prefix as u32 ) as u16 }; + result.push( ( ( palette as u16 ) << 13 ) | tile_id ); + } + + Ok( result ) +} + +/** + * Get the height of a given dmapack bucket (the max of all heights of metatiles in the bucket) + */ +fn get_bucket_height( bucket_id: u16, metatiles: &Vec ) -> usize { + let filtered: Vec<&Metatile> = metatiles.iter().filter( | metatile | metatile.pack.bucket == bucket_id ).collect(); + let mut max_height = 0; + + for metatile in filtered { + max_height = max( max_height, metatile.height ); + } + + max_height as usize } \ No newline at end of file diff --git a/src/reskit/level/system.rs b/src/reskit/level/system.rs index 577148c..7ec1251 100644 --- a/src/reskit/level/system.rs +++ b/src/reskit/level/system.rs @@ -1,7 +1,7 @@ use std::{error::Error, collections::HashMap}; use image::GenericImageView; use crate::reskit::{tileset::image_to_tiles, utility::symbol_to_pascal, cli::settings::TileOrder}; -use super::converter::{TiledTilemap, Layer, SystemPlane, TiledTileset}; +use super::{converter::{TiledTilemap, Layer, SystemPlane, TiledTileset}, dmapack::get_buckets}; /** * Output the .bin and .pal file (using `tileset` tool to build it) containing each of the tiles @@ -118,10 +118,9 @@ pub fn get_tiles( tilemap: &TiledTilemap ) -> Result<(Vec, Vec), Box>> - * For each metatile definition: - * 2 bytes: Width of the metatile, in 16-bit words. - * 2 bytes: Height of the metatile, in 16-bit words. - * (n) bytes: The nametable definition for the metatile. Stamp this into vram on each layer. + * For each `dmapack` bucket: + * Contents of the `dmapack` bucket. Use offsets in headers to access metatiles in `dmapack` + * buckets located in this block. * * <<< INSTANCES >>> * For B, then A layers: @@ -132,13 +131,109 @@ pub fn get_tiles( tilemap: &TiledTilemap ) -> Result<(Vec, Vec), Box Result, Box> { + // Assemble header of offsets + let header_offset = ( tilemap.metatiles.len() * 2 ) + 2; + let mut header: Vec = vec![]; + + // Get offsets for each bucket + let buckets = get_buckets( tilemap )?; + let mut bucket_offsets: HashMap = HashMap::new(); + let mut total_offset = header_offset; + for ( bucket_id, bucket ) in &buckets { + bucket_offsets.insert( *bucket_id, total_offset ); + total_offset += bucket.len(); + } + + // Get the offset for each metatile within the buckets + for metatile in &tilemap.metatiles { + let bucket_offset = bucket_offsets.get( &metatile.pack.bucket ).ok_or( "internal error: no offset for dmapack bucket" )?; + let cell_x = metatile.pack.slot[ 0 ]; + let cell_y = metatile.pack.slot[ 1 ]; + + let metatile_byte_offset: u16 = *bucket_offset as u16 + ( ( ( cell_y * 64 ) + cell_x ) * 2 ); + header.extend( metatile_byte_offset.to_be_bytes() ); + } + + // Assemble definitions + let mut definitions: Vec = vec![]; + + for ( _, bucket ) in &buckets { + for cell in bucket { + if let Some( nametable_entry ) = cell { + definitions.extend( nametable_entry.to_be_bytes() ); + } else { + definitions.extend( ( 0 as u16 ).to_be_bytes() ); + } + } + } + + // Assemble instances + let mut instances: Vec = vec![]; + + let layer_b: Option<&Layer> = tilemap.layers.iter().find( | layer | matches!( layer, Layer::Metatile { system_plane: SystemPlane::MdPlaneB, tiles: _ } ) ); + if let Some( layer_b ) = layer_b { + let tiles = match layer_b { Layer::Metatile { system_plane: _, tiles } => tiles }; + let mut subdefinitions: Vec = vec![]; + + for tile_y in 0..tilemap.height { + for tile_x in 0..tilemap.width { + let item_at = tiles.get( ( tile_y * tilemap.width ) + tile_x ).ok_or( "internal error: tilemap does not correlate to width/height" )?; + if *item_at != 0 { + // What index-ID was that? + let index_id = tilemap.metatiles.iter().position( | metatile | metatile.id == *item_at ).ok_or( "invalid file: metatile id not found" )?; + + // Write the data to subdefinitions + subdefinitions.extend( ( index_id as u16 ).to_be_bytes() ); + subdefinitions.extend( ( ( tile_x * 8 ) as u16 ).to_be_bytes() ); + subdefinitions.extend( ( ( tile_y * 8 ) as u16 ).to_be_bytes() ); + } + } + } + + instances.extend( ( ( subdefinitions.len() / 6 ) as u16 ).to_be_bytes() ); + instances.extend( subdefinitions ); + } else { + instances.extend( ( 0 as u16 ).to_be_bytes() ); + } + + let layer_a: Option<&Layer> = tilemap.layers.iter().find( | layer | matches!( layer, Layer::Metatile { system_plane: SystemPlane::MdPlaneA, tiles: _ } ) ); + if let Some( layer_a ) = layer_a { + let tiles = match layer_a { Layer::Metatile { system_plane: _, tiles } => tiles }; + let mut subdefinitions: Vec = vec![]; + + for tile_y in 0..tilemap.height { + for tile_x in 0..tilemap.width { + let item_at = tiles.get( ( tile_y * tilemap.width ) + tile_x ).ok_or( "internal error: tilemap does not correlate to width/height" )?; + if *item_at != 0 { + // What index-ID was that? + let index_id = tilemap.metatiles.iter().position( | metatile | metatile.id == *item_at ).ok_or( "invalid file: metatile id not found" )?; + + // Write the data to subdefinitions + subdefinitions.extend( ( index_id as u16 ).to_be_bytes() ); + subdefinitions.extend( ( ( tile_x * 8 ) as u16 ).to_be_bytes() ); + subdefinitions.extend( ( ( tile_y * 8 ) as u16 ).to_be_bytes() ); + } + } + } + + instances.extend( ( ( subdefinitions.len() / 6 ) as u16 ).to_be_bytes() ); + instances.extend( subdefinitions ); + } else { + instances.extend( ( 0 as u16 ).to_be_bytes() ); + } + // Write the final result let mut result: Vec = vec![]; + let offset_to_instances = header_offset + definitions.len(); + result.extend( ( offset_to_instances as u16 ).to_be_bytes() ); + result.extend( header ); + result.extend( definitions ); + result.extend( instances ); Ok( result ) } -fn get_tilemap_prefix_palette( target_source: &str, tilesets: &Vec ) -> Result<(usize, Vec>), Box> { +pub fn get_tilemap_prefix_palette( target_source: &str, tilesets: &Vec ) -> Result<(usize, Vec>), Box> { // Everything starts at 1 for the blank buffer tile let mut index: usize = 1;