Level exports - tiles, nametables, collision
parent
dc10213f75
commit
f7b9216680
|
@ -1,23 +1,23 @@
|
||||||
use std::{error::Error, fs::read_to_string};
|
use std::{error::Error, fs::read_to_string};
|
||||||
use image::DynamicImage;
|
use image::{DynamicImage, GenericImageView};
|
||||||
use roxmltree::Node;
|
use roxmltree::Node;
|
||||||
use crate::reskit::utility::print_warning;
|
use crate::reskit::utility::print_warning;
|
||||||
use super::ecs;
|
use super::ecs;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TiledTilemap {
|
pub struct TiledTilemap {
|
||||||
tileset: TiledTileset,
|
pub tileset: TiledTileset,
|
||||||
layers: Vec<Layer>,
|
pub layers: Vec<Layer>,
|
||||||
ecs: Vec<ecs::Entity>,
|
ecs: Vec<ecs::Entity>,
|
||||||
width: usize,
|
pub width: usize,
|
||||||
height: usize,
|
pub height: usize,
|
||||||
tile_width: usize,
|
pub tile_width: usize,
|
||||||
tile_height: usize
|
pub tile_height: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TiledTileset {
|
pub struct TiledTileset {
|
||||||
image: DynamicImage
|
pub image: DynamicImage
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -156,6 +156,10 @@ pub fn get_tiled_tilemap( path: &str ) -> Result<TiledTilemap, Box<dyn Error>> {
|
||||||
let image = tileset.descendants().find( | node | node.tag_name() == "image".into() ).ok_or( "invalid file: no image object" )?;
|
let image = tileset.descendants().find( | node | node.tag_name() == "image".into() ).ok_or( "invalid file: no image object" )?;
|
||||||
let image = image::open( image.attribute( "source" ).ok_or( "invalid file: no source attribute on image" )? )?;
|
let image = image::open( image.attribute( "source" ).ok_or( "invalid file: no source attribute on image" )? )?;
|
||||||
|
|
||||||
|
// Image must be a multiple of 8 (md system)
|
||||||
|
if image.width() % 8 != 0 { return Err( "invalid file: tileset width not multiple of 8" )? }
|
||||||
|
if image.height() % 8 != 0 { return Err( "invalid file: tileset height not multiple of 8" )? }
|
||||||
|
|
||||||
// Get the layers
|
// Get the layers
|
||||||
let layers: Vec<Layer> = map.descendants()
|
let layers: Vec<Layer> = map.descendants()
|
||||||
.filter( | node | node.tag_name() == "layer".into() )
|
.filter( | node | node.tag_name() == "layer".into() )
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod converter;
|
pub mod converter;
|
||||||
pub mod ecs;
|
pub mod ecs;
|
||||||
|
pub mod system;
|
|
@ -0,0 +1,152 @@
|
||||||
|
use std::{error::Error, convert::TryInto};
|
||||||
|
use crate::reskit::tileset::image_to_tiles;
|
||||||
|
use super::converter::{TiledTilemap, Layer, SystemPlane};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output the .bin file (using `tileset` tool to build it) containing each of the tiles
|
||||||
|
* in the Tiled Editor tileset.
|
||||||
|
*/
|
||||||
|
pub fn get_tiles( tilemap: &TiledTilemap ) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let mut palette: [u16; 16] = [ 0; 16 ];
|
||||||
|
let mut all_tiles: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
|
for tile_y in 0..tilemap.tile_height {
|
||||||
|
for tile_x in 0..tilemap.tile_width {
|
||||||
|
let tile = tilemap.tileset.image.clone().crop(
|
||||||
|
tile_x as u32,
|
||||||
|
tile_y as u32,
|
||||||
|
tilemap.tile_width as u32,
|
||||||
|
tilemap.tile_height as u32
|
||||||
|
);
|
||||||
|
|
||||||
|
let tile_bin = image_to_tiles(
|
||||||
|
&tile,
|
||||||
|
&mut palette,
|
||||||
|
"tile"
|
||||||
|
);
|
||||||
|
|
||||||
|
all_tiles.extend( tile_bin );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define result and write palette to top of result
|
||||||
|
let mut result: Vec<u8> = Vec::new();
|
||||||
|
for i in 0..palette.len() {
|
||||||
|
let bytes = palette[ i ].to_be_bytes();
|
||||||
|
for i in 0..2 {
|
||||||
|
result.push( bytes[ i ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend all tiles onto result
|
||||||
|
result.extend( all_tiles );
|
||||||
|
|
||||||
|
Ok( result )
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output the .map file defining the hardware tilemap for the given Tiled Tilemap.
|
||||||
|
* In `--system md`, this outputs tilemap B and then tilemap A
|
||||||
|
*/
|
||||||
|
pub fn get_tilemap( tilemap: &TiledTilemap ) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let layer_b: Option<&Layer> = tilemap.layers.iter().find( | layer | matches!( layer, Layer::Tile { system_plane: SystemPlane::MdPlaneB, tiles: _ } ) );
|
||||||
|
let layer_a: Option<&Layer> = tilemap.layers.iter().find( | layer | matches!( layer, Layer::Tile { system_plane: SystemPlane::MdPlaneA, tiles: _ } ) );
|
||||||
|
|
||||||
|
let md_height_per_tile = tilemap.tile_height / 8;
|
||||||
|
let md_width_per_tile = tilemap.tile_width / 8;
|
||||||
|
|
||||||
|
// Each entry in a `--system md` tilemap is 16 bits
|
||||||
|
let mut nametable: Vec<u16> = vec![ 0; ( tilemap.width * md_width_per_tile ) * ( tilemap.height * md_height_per_tile ) ];
|
||||||
|
|
||||||
|
if let Some( layer_b ) = layer_b {
|
||||||
|
let layer_b = match layer_b {
|
||||||
|
Layer::Tile { system_plane: _, tiles } => tiles,
|
||||||
|
_ => return Err( "internal error: invalid object type" )?
|
||||||
|
};
|
||||||
|
|
||||||
|
for y in 0..tilemap.height {
|
||||||
|
for x in 0..tilemap.width {
|
||||||
|
let target_tile: u32 = *layer_b.get( ( y * tilemap.width ) + x ).ok_or( "internal error: invalid data in tilemap" )?;
|
||||||
|
let target_tile: u16 = target_tile.try_into()?;
|
||||||
|
if target_tile > 0 {
|
||||||
|
let wide_tile_size = md_height_per_tile as u16 * md_width_per_tile as u16;
|
||||||
|
let source_tile = ( target_tile - 1 ) * wide_tile_size;
|
||||||
|
let x = x * md_width_per_tile;
|
||||||
|
let y = y * md_height_per_tile;
|
||||||
|
|
||||||
|
// From the starting point of x, y "stamp" the target tile's indices
|
||||||
|
let mut revolving_counter = 0;
|
||||||
|
for y_stamp in y..( y + md_height_per_tile ) {
|
||||||
|
for x_stamp in x..( x + md_width_per_tile ) {
|
||||||
|
nametable[ ( y_stamp * ( tilemap.width * md_width_per_tile ) ) + x_stamp ] = source_tile + revolving_counter;
|
||||||
|
revolving_counter = ( revolving_counter + 1 ) % wide_tile_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just do the same for layer a
|
||||||
|
// Copy pasted because i'm lazy and tired
|
||||||
|
if let Some( layer_a ) = layer_a {
|
||||||
|
let layer_a = match layer_a {
|
||||||
|
Layer::Tile { system_plane: _, tiles } => tiles,
|
||||||
|
_ => return Err( "internal error: invalid object type" )?
|
||||||
|
};
|
||||||
|
|
||||||
|
for y in 0..tilemap.height {
|
||||||
|
for x in 0..tilemap.width {
|
||||||
|
let target_tile: u32 = *layer_a.get( ( y * tilemap.width ) + x ).ok_or( "internal error: invalid data in tilemap" )?;
|
||||||
|
let target_tile: u16 = target_tile.try_into()?;
|
||||||
|
if target_tile > 0 {
|
||||||
|
let wide_tile_size = md_height_per_tile as u16 * md_width_per_tile as u16;
|
||||||
|
let source_tile = ( target_tile - 1 ) * wide_tile_size;
|
||||||
|
let x = x * md_width_per_tile;
|
||||||
|
let y = y * md_height_per_tile;
|
||||||
|
|
||||||
|
// From the starting point of x, y "stamp" the target tile's indices
|
||||||
|
let mut revolving_counter = 0;
|
||||||
|
for y_stamp in y..( y + md_height_per_tile ) {
|
||||||
|
for x_stamp in x..( x + md_width_per_tile ) {
|
||||||
|
nametable[ ( y_stamp * ( tilemap.width * md_width_per_tile ) ) + x_stamp ] = source_tile + revolving_counter;
|
||||||
|
revolving_counter = ( revolving_counter + 1 ) % wide_tile_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the u16's to a series of u8 data
|
||||||
|
let mut result: Vec<u8> = Vec::new();
|
||||||
|
for i in 0..nametable.len() {
|
||||||
|
let bytes = nametable[ i ].to_be_bytes();
|
||||||
|
for i in 0..2 {
|
||||||
|
result.push( bytes[ i ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok( result )
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the .lvc collision map (u8 sized for the map dimensions, collision areas are either 0 or 1)
|
||||||
|
*/
|
||||||
|
pub fn get_collision_map( tilemap: &TiledTilemap ) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let mut result: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
|
let collision: Option<&Layer> = tilemap.layers.iter().find( | layer | matches!( layer, Layer::Collision { tiles: _ } ) );
|
||||||
|
if let Some( collision ) = collision {
|
||||||
|
let collision = match collision {
|
||||||
|
Layer::Collision { tiles } => tiles,
|
||||||
|
_ => return Err( "internal error: invalid object type" )?
|
||||||
|
};
|
||||||
|
|
||||||
|
for collision_data in collision {
|
||||||
|
let collision_data: u8 = if *collision_data > 0 { 1 } else { 0 };
|
||||||
|
result.push( collision_data );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok( result )
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::reskit::utility;
|
use crate::reskit::utility;
|
||||||
use std::process::exit;
|
use std::{process::exit, error::Error};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use image::{ GenericImageView, DynamicImage };
|
use image::{ GenericImageView, DynamicImage };
|
||||||
|
|
||||||
fn color_to_palette( r: u16, g: u16, b: u16, palette: &mut [u16; 16] ) -> u32 {
|
pub fn color_to_palette( r: u16, g: u16, b: u16, palette: &mut [u16; 16] ) -> u32 {
|
||||||
let final_val =
|
let final_val =
|
||||||
( ( r & 0x00F0 ) >> 4 ) |
|
( ( r & 0x00F0 ) >> 4 ) |
|
||||||
( g & 0x00F0 ) |
|
( g & 0x00F0 ) |
|
||||||
|
@ -41,7 +41,7 @@ fn get_pixel( image: &DynamicImage, palette: &mut [u16; 16], x: u32, y: u32 ) ->
|
||||||
color_to_palette( pixel[ 0 ].into(), pixel[ 1 ].into(), pixel[ 2 ].into(), palette )
|
color_to_palette( pixel[ 0 ].into(), pixel[ 1 ].into(), pixel[ 2 ].into(), palette )
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output_bin( image_filename: &str, output_filename: &str, palette: [u16; 16], body: Vec<u8> ) {
|
pub fn output_bin( output_filename: &str, palette: [u16; 16], body: Vec<u8> ) -> Result<(), Box<dyn Error>> {
|
||||||
let mut output_palette: Vec< u8 > = Vec::new();
|
let mut output_palette: Vec< u8 > = Vec::new();
|
||||||
for i in 0..palette.len() {
|
for i in 0..palette.len() {
|
||||||
let bytes = palette[ i ].to_be_bytes();
|
let bytes = palette[ i ].to_be_bytes();
|
||||||
|
@ -54,13 +54,14 @@ fn output_bin( image_filename: &str, output_filename: &str, palette: [u16; 16],
|
||||||
if let Ok( mut output_file ) = output_try {
|
if let Ok( mut output_file ) = output_try {
|
||||||
output_file.write( &output_palette ).unwrap();
|
output_file.write( &output_palette ).unwrap();
|
||||||
output_file.write( &body ).unwrap();
|
output_file.write( &body ).unwrap();
|
||||||
utility::print_good( format!( "converted file {}", image_filename ).as_str() );
|
|
||||||
|
Ok( () )
|
||||||
} else {
|
} else {
|
||||||
utility::print_error( format!( "could not open filename for output {}", output_filename ).as_str() );
|
return Err( format!( "could not open filename for output {}", output_filename ).as_str() )?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output_inc( image_filename: &str, output_filename: &str, palette: [u16; 16], body: Vec<u8> ) {
|
pub fn output_inc( output_filename: &str, palette: [u16; 16], body: Vec<u8> ) -> Result<(), Box<dyn Error>> {
|
||||||
let mut output_palette: Vec< u8 > = Vec::new();
|
let mut output_palette: Vec< u8 > = Vec::new();
|
||||||
for i in 0..palette.len() {
|
for i in 0..palette.len() {
|
||||||
let bytes = palette[ i ].to_be_bytes();
|
let bytes = palette[ i ].to_be_bytes();
|
||||||
|
@ -111,71 +112,85 @@ fn output_inc( image_filename: &str, output_filename: &str, palette: [u16; 16],
|
||||||
fs::write( output_filename.to_string() + ".h", output_h ).expect( "Could not write header file" );
|
fs::write( output_filename.to_string() + ".h", output_h ).expect( "Could not write header file" );
|
||||||
fs::write( output_filename.to_string() + ".c", output_c ).expect( "Could not write source file" );
|
fs::write( output_filename.to_string() + ".c", output_c ).expect( "Could not write source file" );
|
||||||
|
|
||||||
utility::print_good( format!( "converted file {}", image_filename ).as_str() );
|
Ok( () )
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image_to_tiles( img: &DynamicImage, palette: &mut [u16; 16], tile_order: &str ) -> Vec<u8> {
|
||||||
|
let ( mut max_x, mut max_y ) = img.dimensions();
|
||||||
|
if max_x % 8 != 0 { max_x = ( 8 * ( max_x / 8 ) ) + ( 8 - ( max_x % 8 ) ); }
|
||||||
|
if max_y % 8 != 0 { max_y = ( 8 * ( max_y / 8 ) ) + ( 8 - ( max_y % 8 ) ); }
|
||||||
|
|
||||||
|
let mut body: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
|
if tile_order == "sprite" {
|
||||||
|
/*
|
||||||
|
* Sprite order:
|
||||||
|
* 1 3
|
||||||
|
* 2 4
|
||||||
|
*/
|
||||||
|
for x in ( 0..max_x ).step_by( 8 ) {
|
||||||
|
for y in ( 0..max_y ).step_by( 8 ) {
|
||||||
|
for cell_y in 0..8 {
|
||||||
|
let mut series: u32 = 0;
|
||||||
|
|
||||||
|
for cell_x in 0..8 {
|
||||||
|
let nibble: u32 = get_pixel( &img, palette, cell_x + x, cell_y + y ) << ( ( 7 - cell_x ) * 4 );
|
||||||
|
series = series | nibble;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = series.to_be_bytes();
|
||||||
|
for i in 0..4 {
|
||||||
|
body.push( bytes[ i ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Tile order:
|
||||||
|
* 1 2
|
||||||
|
* 3 4
|
||||||
|
*/
|
||||||
|
for y in ( 0..max_y ).step_by( 8 ) {
|
||||||
|
for x in ( 0..max_x ).step_by( 8 ) {
|
||||||
|
for cell_y in 0..8 {
|
||||||
|
let mut series: u32 = 0;
|
||||||
|
|
||||||
|
for cell_x in 0..8 {
|
||||||
|
let nibble: u32 = get_pixel( &img, palette, cell_x + x, cell_y + y ) << ( ( 7 - cell_x ) * 4 );
|
||||||
|
series = series | nibble;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = series.to_be_bytes();
|
||||||
|
for i in 0..4 {
|
||||||
|
body.push( bytes[ i ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate( image_filename: &str, output_filename: &str, output_mode: &str, tile_order: &str ) {
|
pub fn generate( image_filename: &str, output_filename: &str, output_mode: &str, tile_order: &str ) {
|
||||||
let img = image::open( image_filename );
|
let img = image::open( image_filename );
|
||||||
if let Ok( img ) = img {
|
if let Ok( img ) = img {
|
||||||
let ( mut max_x, mut max_y ) = img.dimensions();
|
|
||||||
if max_x % 8 != 0 { max_x = ( 8 * ( max_x / 8 ) ) + ( 8 - ( max_x % 8 ) ); }
|
|
||||||
if max_y % 8 != 0 { max_y = ( 8 * ( max_y / 8 ) ) + ( 8 - ( max_y % 8 ) ); }
|
|
||||||
|
|
||||||
let mut palette: [u16; 16] = [ 0; 16 ];
|
let mut palette: [u16; 16] = [ 0; 16 ];
|
||||||
let mut body: Vec< u8 > = Vec::new();
|
let body = image_to_tiles( &img, &mut palette, tile_order );
|
||||||
|
|
||||||
if tile_order == "sprite" {
|
|
||||||
/*
|
|
||||||
* Tile order:
|
|
||||||
* 1 3
|
|
||||||
* 2 4
|
|
||||||
*/
|
|
||||||
for x in ( 0..max_x ).step_by( 8 ) {
|
|
||||||
for y in ( 0..max_y ).step_by( 8 ) {
|
|
||||||
for cell_y in 0..8 {
|
|
||||||
let mut series: u32 = 0;
|
|
||||||
|
|
||||||
for cell_x in 0..8 {
|
|
||||||
let nibble: u32 = get_pixel( &img, &mut palette, cell_x + x, cell_y + y ) << ( ( 7 - cell_x ) * 4 );
|
|
||||||
series = series | nibble;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bytes = series.to_be_bytes();
|
|
||||||
for i in 0..4 {
|
|
||||||
body.push( bytes[ i ] );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* Tile order:
|
|
||||||
* 1 2
|
|
||||||
* 3 4
|
|
||||||
*/
|
|
||||||
for y in ( 0..max_y ).step_by( 8 ) {
|
|
||||||
for x in ( 0..max_x ).step_by( 8 ) {
|
|
||||||
for cell_y in 0..8 {
|
|
||||||
let mut series: u32 = 0;
|
|
||||||
|
|
||||||
for cell_x in 0..8 {
|
|
||||||
let nibble: u32 = get_pixel( &img, &mut palette, cell_x + x, cell_y + y ) << ( ( 7 - cell_x ) * 4 );
|
|
||||||
series = series | nibble;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bytes = series.to_be_bytes();
|
|
||||||
for i in 0..4 {
|
|
||||||
body.push( bytes[ i ] );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if output_mode == "bin" {
|
if output_mode == "bin" {
|
||||||
output_bin( image_filename, output_filename, palette, body );
|
if let Err( err ) = output_bin( output_filename, palette, body ) {
|
||||||
|
utility::print_error( &format!( "{}", err ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
utility::print_good( format!( "converted file {}", image_filename ).as_str() );
|
||||||
} else if output_mode == "inc" {
|
} else if output_mode == "inc" {
|
||||||
output_inc( image_filename, output_filename, palette, body );
|
if let Err( err ) = output_inc( output_filename, palette, body ) {
|
||||||
|
utility::print_error( &format!( "{}", err ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
utility::print_good( format!( "converted file {}", image_filename ).as_str() );
|
||||||
} else {
|
} else {
|
||||||
utility::print_error( format!( "invalid output mode {}", output_mode ).as_str() );
|
utility::print_error( format!( "invalid output mode {}", output_mode ).as_str() );
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue