use crate::{ensure_element, ensure_tag_name, get_attribute}; use anyhow::anyhow; use rgb::RGB8; use super::property::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: pub properties: Properties, } impl WangColor { pub fn from_xml(node: roxmltree::Node) -> anyhow::Result { ensure_element!(node); ensure_tag_name!(node, "wangcolor"); let color = { let mut c = get_attribute!(node, "color")? .trim_start_matches("#") .chars(); if let Some(color) = read_color::rgb(&mut c) { Ok(RGB8 { r: color[0], g: color[1], b: color[2], }) } else { Err(anyhow!("could not parse color")) } }?; 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(), }; Ok(WangColor { name: get_attribute!(node, "name")?.to_owned(), color, tile: get_attribute!(node, "tile")?.parse()?, probability, properties, }) } } #[cfg(test)] mod test_wang_color { use crate::lowlevel::property::*; use crate::lowlevel::wangset::WangColor; use crate::parse_property_test; use rgb::RGB8; #[test] fn parse_from_xml() { parse_property_test!( WangColor, r##""##, WangColor { name: "Desert".to_owned(), color: RGB8 { r: 255, g: 0, b: 0 }, tile: 29, probability: 1.0, properties: Properties::default() } ); parse_property_test!( WangColor, r##""##, WangColor { name: "Brick".to_owned(), color: RGB8 { r: 0, g: 255, b: 0 }, tile: 9, probability: 1.0, properties: Properties::default() } ); parse_property_test!( WangColor, r##""##, WangColor { name: "Cobblestone".to_owned(), color: RGB8 { r: 0, g: 0, b: 255 }, tile: 33, probability: 0.8, properties: Properties::default() } ); } #[test] fn parse_from_xml_probability_missing() { parse_property_test!( WangColor, r##""##, WangColor { name: "Desert".to_owned(), color: RGB8 { r: 255, g: 0, b: 0 }, tile: 29, probability: 0.0, properties: Properties::default() } ); } // TODO a test with properties set } /// “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).” /// The values are the indexes of the wangcolors of this set #[derive(PartialEq, Eq, Debug)] pub struct WangId { pub top: u8, pub top_right: u8, pub right: u8, pub bottom_right: u8, pub bottom: u8, pub bottom_left: u8, pub left: u8, pub top_left: u8, } impl WangId { pub fn from_string(wang_id_string: &str) -> anyhow::Result { let ids: Vec<&str> = wang_id_string.split(",").collect(); if ids.len() != 8 { return Err(anyhow!("wrong number of elements in wang id")); } Ok(WangId { top: ids[0].parse()?, top_right: ids[1].parse()?, right: ids[2].parse()?, bottom_right: ids[3].parse()?, bottom: ids[4].parse()?, bottom_left: ids[5].parse()?, left: ids[6].parse()?, top_left: ids[7].parse()?, }) } } #[macro_export] macro_rules! quick_wangid { ( $top: expr, $top_right: expr, $right: expr, $bottom_right: expr, $bottom: expr, $bottom_left: expr, $left: expr, $top_left: expr) => { WangId { top: $top, top_right: $top_right, right: $right, bottom_right: $bottom_right, bottom: $bottom, bottom_left: $bottom_left, left: $left, top_left: $top_left, } }; } #[cfg(test)] mod test_wang_id { use crate::lowlevel::wangset::WangId; use crate::quick_wangid; #[test] fn parse_from_string() { assert_eq!( WangId::from_string("0,1,0,2,0,1,0,1").unwrap(), WangId { top: 0, top_right: 1, right: 0, bottom_right: 2, bottom: 0, bottom_left: 1, left: 0, top_left: 1, } ); assert_eq!( WangId::from_string("0,1,0,2,0,2,0,1").unwrap(), quick_wangid!(0, 1, 0, 2, 0, 2, 0, 1) ); assert_eq!( WangId::from_string("0,2,0,2,0,1,0,1").unwrap(), quick_wangid!(0, 2, 0, 2, 0, 1, 0, 1) ); assert_eq!( WangId::from_string("0,3,0,3,0,1,0,1").unwrap(), quick_wangid!(0, 3, 0, 3, 0, 1, 0, 1) ); assert_eq!( WangId::from_string("0,1,0,3,0,3,0,3").unwrap(), quick_wangid!(0, 1, 0, 3, 0, 3, 0, 3) ); assert_eq!( WangId::from_string("0,1,0,1,0,1,0,1").unwrap(), quick_wangid!(0, 1, 0, 1, 0, 1, 0, 1) ); } } ///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 tile_id: 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, } impl WangTile { pub fn from_xml(node: roxmltree::Node) -> anyhow::Result { ensure_element!(node); ensure_tag_name!(node, "wangtile"); Ok(WangTile { tile_id: get_attribute!(node, "tileid")?.parse()?, wangid: WangId::from_string(get_attribute!(node, "wangid")?)?, }) } } #[cfg(test)] mod test_wang_tile { use crate::lowlevel::wangset::{WangId, WangTile}; use crate::parse_property_test; use crate::quick_wangid; #[test] fn parse_from_xml() { parse_property_test!( WangTile, r##""##, WangTile { tile_id: 2, wangid: quick_wangid!(0, 1, 0, 1, 0, 2, 0, 1) } ); } } /// 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: pub properties: Properties, /// Can contain up to 255: (since Tiled 1.5) pub wangcolor: Vec, /// Can contain any number: pub wangtiles: Vec, } impl WangSet { pub fn from_xml(node: roxmltree::Node) -> anyhow::Result { ensure_element!(node); ensure_tag_name!(node, "wangset"); let properties = match node.children().find(|&c| c.has_tag_name("properties")) { Some(node) => Properties::from_xml(node)?, None => Properties::default(), }; let mut wangcolor = Vec::new(); for wangcolor_node in node.children().filter(|&c| c.has_tag_name("wangcolor")) { wangcolor.push(WangColor::from_xml(wangcolor_node)?); } let mut wangtiles = Vec::new(); for wangtile_node in node.children().filter(|&c| c.has_tag_name("wangtile")) { wangtiles.push(WangTile::from_xml(wangtile_node)?); } Ok(WangSet { name: get_attribute!(node, "name")?.to_owned(), tile: get_attribute!(node, "tile")?.parse()?, properties, wangcolor, wangtiles, }) } } #[cfg(test)] mod test_wang_set { use crate::lowlevel::wangset::*; use crate::parse_property_test; use crate::quick_wangid; macro_rules! wang_tile { ($tile_id:expr, $wang_id:expr) => { WangTile { tile_id: $tile_id, wangid: $wang_id, } }; } #[test] fn parse_desert_example_xml() { parse_property_test!( WangSet, r##" "##, WangSet { name: "Desert".to_owned(), tile: 5, properties: Properties::default(), wangcolor: vec![ WangColor { name: "Desert".to_owned(), color: RGB8 { r: 255, g: 0, b: 0 }, tile: 29, probability: 1.0, properties: Properties::default() }, WangColor { name: "Brick".to_owned(), color: RGB8 { r: 0, g: 255, b: 0 }, tile: 9, probability: 1.0, properties: Properties::default() }, WangColor { name: "Cobblestone".to_owned(), color: RGB8 { r: 0, g: 0, b: 255 }, tile: 33, probability: 1.0, properties: Properties::default() }, WangColor { name: "Dirt".to_owned(), color: RGB8 { r: 255, g: 119, b: 0 }, tile: 14, probability: 1.0, properties: Properties::default() } ], wangtiles: vec![ wang_tile!(0, quick_wangid!(0, 1, 0, 2, 0, 1, 0, 1)), wang_tile!(1, quick_wangid!(0, 1, 0, 2, 0, 2, 0, 1)), wang_tile!(2, quick_wangid!(0, 1, 0, 1, 0, 2, 0, 1)), wang_tile!(3, quick_wangid!(0, 4, 0, 1, 0, 4, 0, 4)), wang_tile!(4, quick_wangid!(0, 4, 0, 4, 0, 1, 0, 4)), wang_tile!(5, quick_wangid!(0, 1, 0, 4, 0, 1, 0, 1)), wang_tile!(6, quick_wangid!(0, 1, 0, 4, 0, 4, 0, 1)), wang_tile!(7, quick_wangid!(0, 1, 0, 1, 0, 4, 0, 1)), wang_tile!(8, quick_wangid!(0, 2, 0, 2, 0, 1, 0, 1)), wang_tile!(9, quick_wangid!(0, 2, 0, 2, 0, 2, 0, 2)), wang_tile!(10, quick_wangid!(0, 1, 0, 1, 0, 2, 0, 2)), wang_tile!(11, quick_wangid!(0, 1, 0, 4, 0, 4, 0, 4)), wang_tile!(12, quick_wangid!(0, 4, 0, 4, 0, 4, 0, 1)), wang_tile!(13, quick_wangid!(0, 4, 0, 4, 0, 1, 0, 1)), wang_tile!(14, quick_wangid!(0, 4, 0, 4, 0, 4, 0, 4)), wang_tile!(15, quick_wangid!(0, 1, 0, 1, 0, 4, 0, 4)), wang_tile!(16, quick_wangid!(0, 2, 0, 1, 0, 1, 0, 1)), wang_tile!(17, quick_wangid!(0, 2, 0, 1, 0, 1, 0, 2)), wang_tile!(18, quick_wangid!(0, 1, 0, 1, 0, 1, 0, 2)), wang_tile!(19, quick_wangid!(0, 2, 0, 1, 0, 2, 0, 2)), wang_tile!(20, quick_wangid!(0, 2, 0, 2, 0, 1, 0, 2)), wang_tile!(21, quick_wangid!(0, 4, 0, 1, 0, 1, 0, 1)), wang_tile!(22, quick_wangid!(0, 4, 0, 1, 0, 1, 0, 4)), wang_tile!(23, quick_wangid!(0, 1, 0, 1, 0, 1, 0, 4)), wang_tile!(24, quick_wangid!(0, 1, 0, 3, 0, 1, 0, 1)), wang_tile!(25, quick_wangid!(0, 1, 0, 3, 0, 3, 0, 1)), wang_tile!(26, quick_wangid!(0, 1, 0, 1, 0, 3, 0, 1)), wang_tile!(27, quick_wangid!(0, 1, 0, 2, 0, 2, 0, 2)), wang_tile!(28, quick_wangid!(0, 2, 0, 2, 0, 2, 0, 1)), wang_tile!(29, quick_wangid!(0, 1, 0, 1, 0, 1, 0, 1)), wang_tile!(30, quick_wangid!(0, 1, 0, 1, 0, 1, 0, 1)), wang_tile!(31, quick_wangid!(0, 1, 0, 1, 0, 1, 0, 1)), wang_tile!(32, quick_wangid!(0, 3, 0, 3, 0, 1, 0, 1)), wang_tile!(33, quick_wangid!(0, 3, 0, 3, 0, 3, 0, 3)), wang_tile!(34, quick_wangid!(0, 1, 0, 1, 0, 3, 0, 3)), wang_tile!(35, quick_wangid!(0, 3, 0, 1, 0, 3, 0, 3)), wang_tile!(36, quick_wangid!(0, 3, 0, 3, 0, 1, 0, 3)), wang_tile!(37, quick_wangid!(0, 1, 0, 1, 0, 1, 0, 1)), wang_tile!(38, quick_wangid!(0, 1, 0, 1, 0, 1, 0, 1)), wang_tile!(39, quick_wangid!(0, 1, 0, 1, 0, 1, 0, 1)), wang_tile!(40, quick_wangid!(0, 3, 0, 1, 0, 1, 0, 1)), wang_tile!(41, quick_wangid!(0, 3, 0, 1, 0, 1, 0, 3)), wang_tile!(42, quick_wangid!(0, 1, 0, 1, 0, 1, 0, 3)), wang_tile!(43, quick_wangid!(0, 1, 0, 3, 0, 3, 0, 3)), wang_tile!(44, quick_wangid!(0, 3, 0, 3, 0, 3, 0, 1)), wang_tile!(45, quick_wangid!(0, 1, 0, 1, 0, 1, 0, 1)), wang_tile!(46, quick_wangid!(0, 1, 0, 1, 0, 1, 0, 1)), wang_tile!(47, quick_wangid!(0, 1, 0, 1, 0, 1, 0, 1)), ] } ); } }