summaryrefslogtreecommitdiff
path: root/src/lowlevel/tileset.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lowlevel/tileset.rs')
-rw-r--r--src/lowlevel/tileset.rs441
1 files changed, 378 insertions, 63 deletions
diff --git a/src/lowlevel/tileset.rs b/src/lowlevel/tileset.rs
index 0e6dc87..9db86c8 100644
--- a/src/lowlevel/tileset.rs
+++ b/src/lowlevel/tileset.rs
@@ -1,9 +1,12 @@
use crate::{ensure_element, ensure_tag_name, get_attribute};
use anyhow::anyhow;
-use rgb::RGB8;
+use super::image::Image;
use super::property::Properties;
+use super::wangset::WangSet;
+
+#[derive(Debug, PartialEq)]
pub enum ObjectAlignment {
Unspecified,
TopLeft,
@@ -23,6 +26,27 @@ impl Default for ObjectAlignment {
}
}
+impl ObjectAlignment {
+ pub fn from_string(string: &str) -> anyhow::Result<ObjectAlignment> {
+ match string {
+ "unspecified" => Ok(ObjectAlignment::Unspecified),
+ "topleft" => Ok(ObjectAlignment::TopLeft),
+ "top" => Ok(ObjectAlignment::Top),
+ "topright" => Ok(ObjectAlignment::TopRight),
+ "left" => Ok(ObjectAlignment::Left),
+ "center" => Ok(ObjectAlignment::Center),
+ "right" => Ok(ObjectAlignment::Right),
+ "bottomleft" => Ok(ObjectAlignment::BottomLeft),
+ "bottom" => Ok(ObjectAlignment::Bottom),
+ "bottomright" => Ok(ObjectAlignment::BottomRight),
+ _ => Err(anyhow!(
+ "object orientation \"{}\" is not supported",
+ string
+ )),
+ }
+ }
+}
+#[derive(Debug, PartialEq)]
pub struct Frame {
/// The local ID of a tile within the parent <tileset>.
tileid: u32,
@@ -31,7 +55,80 @@ pub struct Frame {
duration: u32,
}
+impl Frame {
+ pub fn from_xml(node: roxmltree::Node) -> anyhow::Result<Frame> {
+ ensure_element!(node);
+ ensure_tag_name!(node, "frame");
+ Ok(Frame {
+ tileid: get_attribute!(node, "tileid")?.parse()?,
+ duration: get_attribute!(node, "duration")?.parse()?,
+ })
+ }
+
+ pub fn from_animation_xml(node: roxmltree::Node) -> anyhow::Result<Vec<Frame>> {
+ ensure_element!(node);
+ ensure_tag_name!(node, "animation");
+ let mut frames: Vec<Frame> = Vec::new();
+ for frame_node in node.children() {
+ if frame_node.has_tag_name("frame") {
+ frames.push(Frame::from_xml(frame_node)?)
+ }
+ }
+ Ok(frames)
+ }
+}
+#[cfg(test)]
+mod test_frame {
+ use crate::lowlevel::tileset::Frame;
+ use crate::parse_property_test;
+
+ #[test]
+ fn single_frame() {
+ parse_property_test!(
+ Frame,
+ r##"<frame tileid="37" duration="1000"/>"##,
+ Frame {
+ tileid: 37,
+ duration: 1000
+ }
+ );
+ }
+
+ #[test]
+ fn animation() {
+ let source = r##"<animation>
+ <frame tileid="148" duration="1000"/>
+ <frame tileid="157" duration="1000"/>
+ <frame tileid="166" duration="1000"/>
+ <frame tileid="175" duration="1000"/>
+ </animation>"##;
+ let expected_output = vec![
+ Frame {
+ tileid: 148,
+ duration: 1000,
+ },
+ Frame {
+ tileid: 157,
+ duration: 1000,
+ },
+ Frame {
+ tileid: 166,
+ duration: 1000,
+ },
+ Frame {
+ tileid: 175,
+ duration: 1000,
+ },
+ ];
+
+ let doc = roxmltree::Document::parse(source).unwrap();
+ let elem = doc.root_element();
+ assert_eq!(Frame::from_animation_xml(elem).unwrap(), expected_output);
+ }
+}
+
/// Define properties for a specific tile
+#[derive(Debug, PartialEq)]
pub struct Tile {
/// The local tile ID within its tileset.
pub id: u32,
@@ -52,14 +149,127 @@ pub struct Tile {
pub properties: super::property::Properties,
- //TODO Can contain at most one: <image> (since 0.9), <objectgroup>, <animation>
- pub image: Option<super::image::ImageSource>,
+ pub image: Option<super::image::Image>,
+ // TODO <objectgroup>,
/// 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>>,
}
+impl Tile {
+ pub fn from_xml(node: roxmltree::Node) -> anyhow::Result<Tile> {
+ ensure_element!(node);
+ ensure_tag_name!(node, "tile");
+
+ let id: u32 = get_attribute!(node, "id")?.parse()?;
+
+ let probability: f64 = match node.attribute("probability") {
+ Some(p) => p.parse()?,
+ None => 0f64,
+ };
+
+ let properties: Properties = match node.children().find(|&c| c.has_tag_name("properties")) {
+ Some(node) => Properties::from_xml(node)?,
+ None => Properties::default(),
+ };
+
+ let animation: Option<Vec<Frame>> =
+ match node.children().find(|&c| c.has_tag_name("animation")) {
+ Some(node) => Some(Frame::from_animation_xml(node)?),
+ None => None,
+ };
+
+ let image = if let Some(node) = node.children().find(|&c| c.has_tag_name("image")) {
+ Some(Image::from_xml(node)?)
+ } else {
+ None
+ };
+
+ // TODO <objectgroup>,
+
+ Ok(Tile {
+ id,
+ probability,
+ properties,
+ image,
+ animation,
+ })
+ }
+}
+
+#[cfg(test)]
+mod test_tile {
+ use crate::lowlevel::property::*;
+ use crate::lowlevel::tileset::{Frame, Tile};
+ use crate::parse_property_test;
+
+ #[test]
+ fn basic_tile() {
+ parse_property_test!(
+ Tile,
+ r##"<tile id="13">
+ <properties>
+ <property name="door" value="true"/>
+ </properties>
+ </tile>"##,
+ Tile {
+ id: 13,
+ probability: 0.00,
+ properties: Properties {
+ properties: vec![Property::String {
+ name: "door".to_owned(),
+ value: "true".to_owned()
+ }]
+ },
+ image: None,
+ animation: None
+ }
+ );
+ }
+
+ #[test]
+ fn animated_tile() {
+ parse_property_test!(
+ Tile,
+ r##"<tile id="148">
+ <animation>
+ <frame tileid="148" duration="1000"/>
+ <frame tileid="157" duration="1000"/>
+ <frame tileid="166" duration="1000"/>
+ <frame tileid="175" duration="1000"/>
+ </animation>
+ </tile>"##,
+ Tile {
+ id: 148,
+ probability: 0.00,
+ properties: Properties::default(),
+ image: None,
+ animation: Some(vec![
+ Frame {
+ tileid: 148,
+ duration: 1000,
+ },
+ Frame {
+ tileid: 157,
+ duration: 1000,
+ },
+ Frame {
+ tileid: 166,
+ duration: 1000,
+ },
+ Frame {
+ tileid: 175,
+ duration: 1000,
+ }
+ ])
+ }
+ );
+ }
+
+ // TODO test tile with image
+}
+
/// 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.
@@ -227,66 +437,6 @@ impl Terrain {
}
}
-/// 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).
@@ -386,6 +536,7 @@ mod test_transformations {
}
}
+#[derive(Debug, PartialEq)]
pub struct TileSet {
/// The name of this tileset.
pub name: String,
@@ -437,6 +588,170 @@ pub struct TileSet {
pub transformations: TileSetTransformations,
}
+impl TileSet {
+ pub fn from_xml(node: &roxmltree::Node) -> anyhow::Result<TileSet> {
+ let spacing: usize = match node.attribute("spacing") {
+ Some(s) => s.parse()?,
+ None => 0,
+ };
+
+ let margin: usize = match node.attribute("margin") {
+ Some(s) => s.parse()?,
+ None => 0,
+ };
+
+ let object_alignment = match node.attribute("objectalignment") {
+ Some(child_node) => ObjectAlignment::from_string(child_node)?,
+ None => ObjectAlignment::default(),
+ };
+
+ let mut tiles: Vec<Tile> = Vec::new();
+ for tile_node in node.children() {
+ if tile_node.has_tag_name("tile") {
+ tiles.push(Tile::from_xml(tile_node)?)
+ }
+ }
+
+ let image = match node.children().find(|&c| c.has_tag_name("image")) {
+ Some(child_node) => Some(Image::from_xml(child_node)?),
+ None => None,
+ };
+
+ let tile_offset = match node.children().find(|&c| c.has_tag_name("tileoffset")) {
+ Some(tile_offset_node) => TileOffset::from_xml(tile_offset_node)?,
+ None => TileOffset::default(),
+ };
+
+ let grid: Option<Grid> = match node.children().find(|&c| c.has_tag_name("grid")) {
+ Some(child_node) => Some(Grid::from_xml(child_node)?),
+ None => None,
+ };
+
+ let properties: Properties = match node.children().find(|&c| c.has_tag_name("properties")) {
+ Some(child_node) => Properties::from_xml(child_node)?,
+ None => Properties::default(),
+ };
+
+ let transformations = match node.children().find(|&c| c.has_tag_name("transformations")) {
+ Some(child_node) => TileSetTransformations::from_xml(child_node)?,
+ None => TileSetTransformations::default(),
+ };
+
+ Ok(TileSet {
+ name: get_attribute!(node, "name")?.to_owned(),
+ tile_width: get_attribute!(node, "tilewidth")?.parse()?,
+ tile_height: get_attribute!(node, "tileheight")?.parse()?,
+ spacing,
+ margin,
+ tile_count: get_attribute!(node, "tilecount")?.parse()?,
+ columns: get_attribute!(node, "columns")?.parse()?,
+ object_alignment,
+ tile: tiles,
+ image,
+ tile_offset,
+ grid,
+ properties,
+ terrain_types: None, // TODO
+ wangsets: None, // TODO
+ transformations,
+ })
+ }
+}
+
+#[cfg(test)]
+mod test_tileset {
+ use crate::lowlevel::image::*;
+ use crate::lowlevel::tileset::*;
+ use std::fs;
+
+ #[test]
+ fn example_beach_tileset() {
+ let file = fs::read_to_string("testing_data/tiled_examples/rpg/beach_tileset.tsx").unwrap();
+ let doc = roxmltree::Document::parse(&file).unwrap();
+ let elem = doc.root_element();
+ let tile_set = TileSet::from_xml(&elem).unwrap();
+ assert_eq!(
+ tile_set,
+ TileSet {
+ name: "beach_tileset".to_owned(),
+ tile_width: 16,
+ tile_height: 16,
+ spacing: 0,
+ margin: 0,
+ tile_count: 936,
+ columns: 36,
+ object_alignment: ObjectAlignment::Unspecified,
+ tile: vec![
+ Tile {
+ id: 37,
+ probability: 0.0,
+ properties: Properties::default(),
+ image: None,
+ animation: Some(vec![
+ Frame {
+ tileid: 37,
+ duration: 1000
+ },
+ Frame {
+ tileid: 46,
+ duration: 1000
+ },
+ Frame {
+ tileid: 55,
+ duration: 1000
+ },
+ Frame {
+ tileid: 64,
+ duration: 1000
+ }
+ ])
+ },
+ Tile {
+ id: 148,
+ probability: 0.0,
+ properties: Properties::default(),
+ image: None,
+ animation: Some(vec![
+ Frame {
+ tileid: 148,
+ duration: 1000
+ },
+ Frame {
+ tileid: 157,
+ duration: 1000
+ },
+ Frame {
+ tileid: 166,
+ duration: 1000
+ },
+ Frame {
+ tileid: 175,
+ duration: 1000
+ }
+ ])
+ }
+ ],
+ image: Some(Image {
+ source: ImageSource::External {
+ source: "beach_tileset.png".to_owned()
+ },
+ transparent_color: None,
+ width: Some(576),
+ height: Some(416)
+ }),
+ tile_offset: TileOffset::default(),
+ grid: None,
+ properties: Properties::default(),
+ terrain_types: None,
+ wangsets: None,
+ transformations: TileSetTransformations::default()
+ }
+ );
+ }
+
+ // todo more tests
+}
+
pub enum TileSetElement {
Embedded { first_gid: usize, tileset: TileSet },
External { first_gid: usize, source: String },