diff options
Diffstat (limited to 'src/lowlevel/tileset.rs')
-rw-r--r-- | src/lowlevel/tileset.rs | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/src/lowlevel/tileset.rs b/src/lowlevel/tileset.rs new file mode 100644 index 0000000..0e6dc87 --- /dev/null +++ b/src/lowlevel/tileset.rs @@ -0,0 +1,443 @@ +use crate::{ensure_element, ensure_tag_name, get_attribute}; +use anyhow::anyhow; +use rgb::RGB8; + +use super::property::Properties; + +pub enum ObjectAlignment { + Unspecified, + TopLeft, + Top, + TopRight, + Left, + Center, + Right, + BottomLeft, + Bottom, + BottomRight, +} + +impl Default for ObjectAlignment { + fn default() -> Self { + ObjectAlignment::Unspecified + } +} + +pub struct Frame { + /// The local ID of a tile within the parent <tileset>. + tileid: u32, + + // How long (in milliseconds) this frame should be displayed before advancing to the next frame. + duration: u32, +} + +/// Define properties for a specific tile +pub struct Tile { + /// The local tile ID within its tileset. + pub id: u32, + /// The type of the tile. + /// Refers to an object type and is used by tile objects. (optional) (since 1.0) + // TODO pub tile_type: Option<>, + + /// Defines the terrain type of each corner of the tile, + /// given as comma-separated indexes in the terrain types array + /// in the order top-left, top-right, bottom-left, bottom-right. + /// Leaving out a value means that corner has no terrain. (optional) + // TODO pub terrain: Option<>, + + /// A percentage indicating the probability that this tile is chosen + /// when it competes with others while editing with the terrain tool. + /// (defaults to 0) + pub probability: f64, + + pub properties: super::property::Properties, + + //TODO Can contain at most one: <image> (since 0.9), <objectgroup>, <animation> + pub image: Option<super::image::ImageSource>, + + /// Each tile can have exactly one animation associated with it. + /// In the future, there could be support for multiple named animations on a tile. + pub animation: Option<Vec<Frame>>, +} + +/// This element is used to specify an offset in pixels, +/// to be applied when drawing a tile from the related tileset. +/// When not present, no offset is applied. +#[derive(Eq, PartialEq, Debug)] +pub struct TileOffset { + /// Horizontal offset in pixels. (defaults to 0) + x: isize, + /// Vertical offset in pixels (positive is down, defaults to 0) + y: isize, +} + +impl TileOffset { + fn from_xml(node: roxmltree::Node) -> anyhow::Result<TileOffset> { + ensure_element!(node); + ensure_tag_name!(node, "tileoffset"); + + Ok(TileOffset { + x: get_attribute!(node, "x")?.parse()?, + y: get_attribute!(node, "y")?.parse()?, + }) + } +} + +impl Default for TileOffset { + fn default() -> Self { + TileOffset { x: 0, y: 0 } + } +} + +#[cfg(test)] +mod test_tile_offset { + use crate::lowlevel::tileset::TileOffset; + + #[test] + fn default_value() { + let default: TileOffset = Default::default(); + assert_eq!(default.x, 0); + assert_eq!(default.y, 0); + } + + #[test] + fn from_xml() { + crate::parse_property_test!( + TileOffset, + r##"<tileoffset x="32" y="16" />"##, + TileOffset { x: 32, y: 16 } + ); + } +} + +/// Orientation of the grid for the tiles in this tileset (orthogonal or isometric, defaults to orthogonal) +#[derive(Eq, PartialEq, Debug)] +pub enum GridOrientation { + Orthogonal, + Isometric, +} + +impl Default for GridOrientation { + fn default() -> Self { + GridOrientation::Orthogonal + } +} + +/// This element is only used in case of isometric orientation, +/// and determines how tile overlays for terrain and collision information are rendered. +#[derive(Eq, PartialEq, Debug)] +pub struct Grid { + orientation: GridOrientation, + /// Width of a grid cell + width: u32, + /// Height of a grid cell + height: u32, +} + +impl Grid { + fn from_xml(node: roxmltree::Node) -> anyhow::Result<Grid> { + ensure_element!(node); + ensure_tag_name!(node, "grid"); + let orientation: GridOrientation = match node.attribute("orientation") { + Some("orthogonal") => GridOrientation::Orthogonal, + Some("isometric") => GridOrientation::Isometric, + _ => GridOrientation::default(), + }; + + Ok(Grid { + orientation, + width: get_attribute!(node, "width")?.parse()?, + height: get_attribute!(node, "height")?.parse()?, + }) + } +} + +#[cfg(test)] +mod test_grid { + use crate::lowlevel::tileset::{Grid, GridOrientation}; + + #[test] + fn grid_orientation_default_value() { + let default: GridOrientation = Default::default(); + assert_eq!(default, GridOrientation::Orthogonal); + } + + #[test] + fn from_xml() { + crate::parse_property_test!( + Grid, + r##"<grid orientation="isometric" height="32" width="32" />"##, + Grid { + orientation: GridOrientation::Isometric, + height: 32, + width: 32 + } + ); + crate::parse_property_test!( + Grid, + r##"<grid orientation="orthogonal" height="32" width="32" />"##, + Grid { + orientation: GridOrientation::Orthogonal, + height: 32, + width: 32 + } + ); + } + + #[test] + fn from_xml_without_explicit_orientation() { + crate::parse_property_test!( + Grid, + r##"<grid height="32" width="32" />"##, + Grid { + orientation: GridOrientation::default(), + height: 32, + width: 32 + } + ); + } +} + +#[derive(PartialEq, Debug)] +pub struct Terrain { + /// The name of the terrain type. + pub name: String, + /// The local tile-id of the tile that represents the terrain visually. + pub tile: u32, + pub properties: Properties, +} + +impl Terrain { + fn from_xml(node: roxmltree::Node) -> anyhow::Result<Terrain> { + ensure_element!(node); + ensure_tag_name!(node, "terrain"); + + let name = get_attribute!(node, "name")?.to_string(); + let tile = get_attribute!(node, "tile")?.parse()?; + let properties: Properties = match node.children().find(|&c| c.has_tag_name("properties")) { + Some(node) => Properties::from_xml(node)?, + None => Properties::default(), + }; + + Ok(Terrain { + name, + tile, + properties, + }) + } +} + +/// A color that can be used to define the corner and/or edge of a Wang tile. +#[derive(PartialEq, Debug)] +pub struct WangColor { + /// The name of this color. + pub name: String, + /// The color in #RRGGBB format (example: #c17d11). + pub color: RGB8, + /// The local tile ID of the tile representing this color. + pub tile: u32, + /// The relative probability that this color is chosen over others in case of multiple options. + /// (defaults to 0) + pub probability: f64, + + // Can contain at most one: <properties> + pub properties: Properties, +} + +/// “The Wang ID, given by a comma-separated list of indexes +/// (starting from 1, because 0 means _unset_) +/// referring to the Wang colors in the Wang set in the following order: +/// top, top right, right, bottom right, bottom, bottom left, left, top left (since Tiled 1.5). +/// Before Tiled 1.5, the Wang ID was saved as a 32-bit unsigned integer +/// stored in the format 0xCECECECE (where each C is a corner color and each E is an edge color, +/// in reverse order).” +#[derive(PartialEq, Debug)] +pub struct WangId {} + +///Defines a Wang tile, by referring to a tile in the tileset and associating it with a certain Wang ID. +#[derive(PartialEq, Debug)] +pub struct WangTile { + /// The tile ID. + pub tileid: u32, + /// The Wang ID + pub wangid: WangId, + /// Whether the tile is flipped horizontally (removed in Tiled 1.5). + pub hflip: bool, + /// Whether the tile is flipped vertically (removed in Tiled 1.5). + pub vflip: bool, + /// Whether the tile is flipped on its diagonal (removed in Tiled 1.5). + pub dflip: bool, +} + +/// Defines a list of corner colors and a list of edge colors, +/// and any number of Wang tiles using these colors. +#[derive(PartialEq, Debug)] +pub struct WangSet { + ///The name of the Wang set. + pub name: String, + /// The tile ID of the tile representing this Wang set. + pub tile: u32, + + // Can contain at most one: <properties> + pub properties: Properties, + + /// Can contain up to 255: <wangcolor> (since Tiled 1.5) + pub wangcolor: Vec<WangColor>, + + /// Can contain any number: <wangtile> + pub wangtiles: Vec<WangTile>, +} + +/// This element is used to describe which transformations can be applied to the tiles +/// (e.g. to extend a Wang set by transforming existing tiles). +#[derive(PartialEq, Debug)] +pub struct TileSetTransformations { + /// Whether the tiles in this set can be flipped horizontally (default 0) + pub hflip: bool, + /// Whether the tiles in this set can be flipped vertically (default 0) + pub vflip: bool, + /// Whether the tiles in this set can be rotated in 90 degree increments (default 0) + pub rotate: bool, + /// Whether untransformed tiles remain preferred, otherwise transformed tiles are used to produce more variations (default 0) + pub prefer_untransformed: bool, +} + +impl Default for TileSetTransformations { + fn default() -> Self { + TileSetTransformations { + hflip: false, + vflip: false, + rotate: false, + prefer_untransformed: false, + } + } +} + +impl TileSetTransformations { + pub fn from_xml(node: roxmltree::Node) -> anyhow::Result<TileSetTransformations> { + ensure_element!(node); + ensure_tag_name!(node, "transformations"); + macro_rules! num_string_to_bool { + ($attribute_name:expr) => { + match node.attribute($attribute_name) { + Some("1") => Ok(true), + Some("0") => Ok(false), + Some(val) => Err(anyhow!( + "Unexpected value \"{}\" for {}. Only 0->false and 1->true are allowed", + val, + $attribute_name + )), + None => Ok(false), + } + }; + } + + Ok(TileSetTransformations { + hflip: num_string_to_bool!("hflip")?, + vflip: num_string_to_bool!("vflip")?, + rotate: num_string_to_bool!("rotate")?, + prefer_untransformed: num_string_to_bool!("preferuntransformed")?, + }) + } +} + +//TODO TileSetTransformations tests (parse test and test for default) + +#[cfg(test)] +mod test_transformations { + use crate::lowlevel::tileset::TileSetTransformations; + + #[test] + fn default_value() { + let default: TileSetTransformations = Default::default(); + assert_eq!( + default, + TileSetTransformations { + hflip: false, + vflip: false, + rotate: false, + prefer_untransformed: false, + } + ); + } + + #[test] + fn from_xml() { + crate::parse_property_test!( + TileSetTransformations, + r##"<transformations hflip="1" vflip="1" rotate="1" preferuntransformed="1" />"##, + TileSetTransformations { + hflip: true, + vflip: true, + rotate: true, + prefer_untransformed: true, + } + ); + crate::parse_property_test!( + TileSetTransformations, + r##"<transformations hflip="1" vflip="0" rotate="1" preferuntransformed="0" />"##, + TileSetTransformations { + hflip: true, + vflip: false, + rotate: true, + prefer_untransformed: false, + } + ); + } +} + +pub struct TileSet { + /// The name of this tileset. + pub name: String, + /// The (maximum) width of the tiles in this tileset. + pub tile_width: usize, + + /// The (maximum) height of the tiles in this tileset. + pub tile_height: usize, + /// The spacing in pixels between the tiles in this tileset (applies to the tileset image, defaults to 0) + pub spacing: usize, + + /// The margin around the tiles in this tileset (applies to the tileset image, defaults to 0) + pub margin: usize, + + /// The number of tiles in this tileset (since 0.13) + pub tile_count: usize, + + /// The number of tile columns in the tileset. + /// For image collection tilesets it is editable and is used when displaying the tileset. (since 0.15) + pub columns: usize, + + /// Controls the alignment for tile objects. + /// Valid values are unspecified, topleft, top, topright, left, center, right, bottomleft, bottom and bottomright. + /// The default value is unspecified, for compatibility reasons. + /// When unspecified, tile objects use bottomleft in orthogonal mode and bottom in isometric mode. (since 1.4) + pub object_alignment: ObjectAlignment, + + /// Define properties for specific tiles. + /// This doesn't necessarily contain all tiles (only tiles that have properties, like animations or image tiles) + /// In fact most of the time it doesn't contain tiles. + pub tile: Vec<Tile>, + + pub image: Option<super::image::Image>, + + /// this field is optional, if not set it will get {x:0, y:0} (no offset) + pub tile_offset: TileOffset, + + /// (since 1.0) + pub grid: Option<Grid>, + + pub properties: super::property::Properties, + + pub terrain_types: Option<Vec<Terrain>>, + + /// (since 1.1) + pub wangsets: Option<Vec<WangSet>>, + + /// (since 1.5) + pub transformations: TileSetTransformations, +} + +pub enum TileSetElement { + Embedded { first_gid: usize, tileset: TileSet }, + External { first_gid: usize, source: String }, +} |