diff --git a/assets/tiled_base64_zlib_infinite.tmx b/assets/tiled_base64_zlib_infinite.tmx new file mode 100644 index 0000000000000000000000000000000000000000..f8398d24127df606ea7d149f1f9754a950b505ce --- /dev/null +++ b/assets/tiled_base64_zlib_infinite.tmx @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<map version="1.2" tiledversion="2020.05.20" orientation="orthogonal" renderorder="right-down" width="100" height="100" tilewidth="32" tileheight="32" infinite="1" backgroundcolor="#ff00ff" nextlayerid="6" nextobjectid="5"> + <editorsettings> + <chunksize width="32" height="32"/> + </editorsettings> + <tileset firstgid="1" name="tilesheet" tilewidth="32" tileheight="32" tilecount="84" columns="14"> + <image source="tilesheet.png" width="448" height="192"/> + <tile id="1"> + <properties> + <property name="a tile property" value="123"/> + </properties> + </tile> + </tileset> + <tileset firstgid="85" source="tilesheet.tsx"/> + <layer id="3" name="Background" width="100" height="100"> + <data encoding="base64" compression="zlib"> + <chunk x="-32" y="0" width="32" height="32"> + eJztzTENAAAMw7BiGH+wg9CjryPldrJ142t8Pp/P5/P5fD6fz+fz+w/olSQB + </chunk> + <chunk x="0" y="0" width="32" height="32"> + eJztwwEJAAAMBKHL8P3DrsdQcNVUVVXV1w/BwEgB + </chunk> + <chunk x="-32" y="32" width="32" height="32"> + eJztzcEJAAAIA7HO4P7DOkRBEBK49yWdKWv5+/v7+/v73/8BgH8WAIoSAQ== + </chunk> + <chunk x="0" y="32" width="32" height="32"> + eJztwwEJAAAAAqA29H9sQ1KwSaqqXgUA/gxxUCQB + </chunk> + </data> + </layer> + <layer id="4" name="Ground" width="100" height="100" locked="1"> + <data encoding="base64" compression="zlib"> + <chunk x="0" y="0" width="32" height="32"> + eJztVLsOwjAQi4Af6MAnIBb4AgRiRUKw8+hMS2fg87mInGSdLk0ThgyNJYtXajvxBWMKCgoK4nAhXom3TN4dsSE+XY5/s/B+NErdB/FOfANryJKSw2o2HrYiZ+e+q4AvkSM2Az/PXMD7j5KzFc9jjlr5PYRKcCI+h/xRx66JPQN7rivi2r0ujd4/+/fpp5yB1Z0Dff3jnODsS3aBjJq/1ZRzLfvnvflmtW92Q/4zRzkL2D/efS0r34EU/yHr8fzlHWTKNUP9D6a/U+6V9/5RldL9pyau10pVSvPn/58NUOtWm0mfVmgdgrvbmd/92xu9W20mfVqhdRrQPwfG7n8knomnTP4W24zexX9c/l+dHlQo + </chunk> + <chunk x="0" y="32" width="32" height="32"> + eJzt0KESABAQRdH9IwlJIlH8/9fYQKJ66Z6ZV5S7w+xWHm9Kpx99w5fE/by70xdM/x+nV/cdTdzv4h4AAAAA4L8FOaQDyA== + </chunk> + </data> + </layer> + <layer id="5" name="Overlay" width="100" height="100"> + <data encoding="base64" compression="zlib"> + <chunk x="0" y="0" width="32" height="32"> + eJztwzENAAAIA7A5wb9L3hkgPG3SBAB+TQUA4MYCfd0AXg== + </chunk> + </data> + </layer> + <objectgroup id="2" name="Object group"> + <object id="1" x="14" y="9" width="285" height="135"/> + <object id="2" x="329" y="217" width="102" height="109"> + <ellipse/> + </object> + <object id="3" x="314" y="376"> + <polyline points="0,0 -111,-63 -203,27 -205,-130 -78,-150 -6,-6"/> + </object> + <object id="4" x="479" y="84"> + <polygon points="0,0 139,128 -55,64 -37,-49 159,47 138,126"/> + </object> + </objectgroup> +</map> diff --git a/src/lib.rs b/src/lib.rs index cd201360dd4eddcb5dd8791913fbd5682f1e197c..e873e8519188ffca526257d9f4fc8d3917466909 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -236,6 +236,7 @@ pub struct Map { pub object_groups: Vec<ObjectGroup>, pub properties: Properties, pub background_colour: Option<Colour>, + pub infinite: bool, } impl Map { @@ -244,10 +245,11 @@ impl Map { attrs: Vec<OwnedAttribute>, map_path: Option<&Path>, ) -> Result<Map, TiledError> { - let (c, (v, o, w, h, tw, th)) = get_attrs!( + let ((c, infinite), (v, o, w, h, tw, th)) = get_attrs!( attrs, optionals: [ ("backgroundcolor", colour, |v:String| v.parse().ok()), + ("infinite", infinite, |v:String| Some(v == "1")), ], required: [ ("version", version, |v| Some(v)), @@ -272,7 +274,7 @@ impl Map { Ok(()) }, "layer" => |attrs| { - layers.push(Layer::new(parser, attrs, w, layer_index)?); + layers.push(Layer::new(parser, attrs, w, layer_index, infinite.unwrap_or(false))?); layer_index += 1; Ok(()) }, @@ -304,6 +306,7 @@ impl Map { object_groups, properties, background_colour: c, + infinite: infinite.unwrap_or(false) }) } @@ -666,7 +669,7 @@ pub struct Layer { pub visible: bool, /// The tiles are arranged in rows. Each tile is a number which can be used /// to find which tileset it belongs to and can then be rendered. - pub tiles: Vec<Vec<LayerTile>>, + pub tiles: LayerData, pub properties: Properties, pub layer_index: u32, } @@ -677,6 +680,7 @@ impl Layer { attrs: Vec<OwnedAttribute>, width: u32, layer_index: u32, + infinite: bool, ) -> Result<Layer, TiledError> { let ((o, v), n) = get_attrs!( attrs, @@ -689,11 +693,15 @@ impl Layer { ], TiledError::MalformedAttributes("layer must have a name".to_string()) ); - let mut tiles = Vec::new(); + let mut tiles: LayerData = LayerData::Finite(Default::default()); let mut properties = HashMap::new(); parse_tag!(parser, "layer", { "data" => |attrs| { - tiles = parse_data(parser, attrs, width)?; + if infinite { + tiles = parse_infinite_data(parser, attrs, width)?; + } else { + tiles = parse_data(parser, attrs, width)?; + } Ok(()) }, "properties" => |_| { @@ -712,6 +720,55 @@ impl Layer { }) } } +#[derive(Debug, PartialEq, Clone)] +pub enum LayerData { + Finite(Vec<Vec<LayerTile>>), + Infinite(HashMap<(i32, i32), Chunk>) +} + +#[derive(Debug, PartialEq, Clone)] +pub struct Chunk { + pub x: i32, + pub y: i32, + pub width: u32, + pub height: u32, + pub tiles: Vec<Vec<LayerTile>>, +} + + +impl Chunk { + pub(crate) fn new<R: Read>( + parser: &mut EventReader<R>, + attrs: Vec<OwnedAttribute>, + encoding: Option<String>, + compression: Option<String>, + ) -> Result<Chunk, TiledError> { + let ((), (x, y, width, height)) = get_attrs!( + attrs, + optionals: [], + required: [ + ("x", x, |v: String| v.parse().ok()), + ("y", y, |v: String| v.parse().ok()), + ("width", width, |v: String| v.parse().ok()), + ("height", height, |v: String| v.parse().ok()), + ], + TiledError::MalformedAttributes("layer must have a name".to_string()) + ); + + + + let tiles = parse_data_line(encoding, compression, parser, width)?; + + Ok(Chunk { + x, + y, + width, + height, + tiles, + }) + } +} + #[derive(Debug, PartialEq, Clone)] pub struct ImageLayer { @@ -1007,11 +1064,38 @@ fn parse_animation<R: Read>(parser: &mut EventReader<R>) -> Result<Vec<Frame>, T Ok(animation) } +fn parse_infinite_data<R: Read>( + parser: &mut EventReader<R>, + attrs: Vec<OwnedAttribute>, + width: u32, +) -> Result<LayerData, TiledError> { + let ((e, c), ()) = get_attrs!( + attrs, + optionals: [ + ("encoding", encoding, |v| Some(v)), + ("compression", compression, |v| Some(v)), + ], + required: [], + TiledError::MalformedAttributes("data must have an encoding and a compression".to_string()) + ); + + let mut chunks = HashMap::<(i32, i32), Chunk>::new(); + parse_tag!(parser, "data", { + "chunk" => |attrs| { + let chunk = Chunk::new(parser, attrs, e.clone(), c.clone())?; + chunks.insert((chunk.x, chunk.y), chunk); + Ok(()) + } + }); + + Ok(LayerData::Infinite(chunks)) +} + fn parse_data<R: Read>( parser: &mut EventReader<R>, attrs: Vec<OwnedAttribute>, width: u32, -) -> Result<Vec<Vec<LayerTile>>, TiledError> { +) -> Result<LayerData, TiledError> { let ((e, c), ()) = get_attrs!( attrs, optionals: [ @@ -1022,7 +1106,13 @@ fn parse_data<R: Read>( TiledError::MalformedAttributes("data must have an encoding and a compression".to_string()) ); - match (e, c) { + let tiles = parse_data_line(e, c, parser, width)?; + + Ok(LayerData::Finite(tiles)) +} + +fn parse_data_line<R: Read>(encoding: Option<String>, compression: Option<String>, parser: &mut EventReader<R>, width: u32) -> Result<Vec<Vec<LayerTile>>, TiledError> { + match (encoding, compression) { (None, None) => { return Err(TiledError::Other( "XML format is currently not supported".to_string(), diff --git a/tests/lib.rs b/tests/lib.rs index 3aca62930f94318a4b77332b58fe5523821ec881..957fa8e2c9cd9819bf8d54081e73d8666385c4d5 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,6 +1,6 @@ use std::fs::File; use std::path::Path; -use tiled::{parse, parse_file, parse_tileset, Map, PropertyValue, TiledError}; +use tiled::{parse, parse_file, parse_tileset, Map, PropertyValue, TiledError, LayerData}; fn read_from_file(p: &Path) -> Result<Map, TiledError> { let file = File::open(p).unwrap(); @@ -36,6 +36,24 @@ fn test_just_tileset() { assert_eq!(r.tilesets[0], t); } +#[test] +fn test_infinite_tileset() { + let r = read_from_file_with_path(&Path::new("assets/tiled_base64_zlib_infinite.tmx")).unwrap(); + + if let LayerData::Infinite(chunks) = &r.layers[0].tiles { + assert_eq!(chunks.len(), 4); + + assert_eq!(chunks[&(0, 0)].width, 32); + assert_eq!(chunks[&(0, 0)].height, 32); + assert_eq!(chunks[&(-32, 0)].width, 32); + assert_eq!(chunks[&(0, 32)].height, 32); + assert_eq!(chunks[&(-32, 32)].height, 32); + } else { + assert!(false, "It is wrongly recognized as a finite map"); + + } +} + #[test] fn test_image_layers() { let r = read_from_file(&Path::new("assets/tiled_image_layers.tmx")).unwrap(); @@ -104,23 +122,29 @@ fn test_tileset_property() { #[test] fn test_flipped_gid() { let r = read_from_file_with_path(&Path::new("assets/tiled_flipped.tmx")).unwrap(); - let t1 = r.layers[0].tiles[0][0]; - let t2 = r.layers[0].tiles[0][1]; - let t3 = r.layers[0].tiles[1][0]; - let t4 = r.layers[0].tiles[1][1]; - assert_eq!(t1.gid, t2.gid); - assert_eq!(t2.gid, t3.gid); - assert_eq!(t3.gid, t4.gid); - assert!(t1.flip_d); - assert!(t1.flip_h); - assert!(t1.flip_v); - assert!(!t2.flip_d); - assert!(!t2.flip_h); - assert!(t2.flip_v); - assert!(!t3.flip_d); - assert!(t3.flip_h); - assert!(!t3.flip_v); - assert!(t4.flip_d); - assert!(!t4.flip_h); - assert!(!t4.flip_v); + + if let LayerData::Finite(tiles) = &r.layers[0].tiles { + let t1 = tiles[0][0]; + let t2 = tiles[0][1]; + let t3 = tiles[1][0]; + let t4 = tiles[1][1]; + assert_eq!(t1.gid, t2.gid); + assert_eq!(t2.gid, t3.gid); + assert_eq!(t3.gid, t4.gid); + assert!(t1.flip_d); + assert!(t1.flip_h); + assert!(t1.flip_v); + assert!(!t2.flip_d); + assert!(!t2.flip_h); + assert!(t2.flip_v); + assert!(!t3.flip_d); + assert!(t3.flip_h); + assert!(!t3.flip_v); + assert!(t4.flip_d); + assert!(!t4.flip_h); + assert!(!t4.flip_v); + } else { + assert!(false, "It is wrongly recognized as an infinite map"); + } + }