From d775eb1e9c6be67e5c07cabde79145b4ed3136e1 Mon Sep 17 00:00:00 2001 From: LawnCable Date: Fri, 15 Apr 2022 08:37:04 +0200 Subject: improvements, wangset and images alse parsing for may tileset related types --- src/lowlevel/wangset.rs | 484 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 484 insertions(+) create mode 100644 src/lowlevel/wangset.rs (limited to 'src/lowlevel/wangset.rs') diff --git a/src/lowlevel/wangset.rs b/src/lowlevel/wangset.rs new file mode 100644 index 0000000..421ccd0 --- /dev/null +++ b/src/lowlevel/wangset.rs @@ -0,0 +1,484 @@ +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)), + ] + } + ); + } +} -- cgit v1.2.3-60-g2f50