summaryrefslogtreecommitdiff
path: root/src/lowlevel/wangset.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lowlevel/wangset.rs')
-rw-r--r--src/lowlevel/wangset.rs484
1 files changed, 484 insertions, 0 deletions
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: <properties>
+ pub properties: Properties,
+}
+
+impl WangColor {
+ pub fn from_xml(node: roxmltree::Node) -> anyhow::Result<WangColor> {
+ 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" color="#ff0000" tile="29" probability="1"/>"##,
+ 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" color="#00ff00" tile="9" probability="1"/>"##,
+ 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" color="#0000ff" tile="33" probability="0.8"/>"##,
+ 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" color="#ff0000" tile="29" />"##,
+ 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<WangId> {
+ 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<WangTile> {
+ 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 tileid="2" wangid="0,1,0,1,0,2,0,1"/>"##,
+ 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: <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>,
+}
+
+impl WangSet {
+ pub fn from_xml(node: roxmltree::Node) -> anyhow::Result<WangSet> {
+ 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" type="corner" tile="5">
+ <wangcolor name="Desert" color="#ff0000" tile="29" probability="1"/>
+ <wangcolor name="Brick" color="#00ff00" tile="9" probability="1"/>
+ <wangcolor name="Cobblestone" color="#0000ff" tile="33" probability="1"/>
+ <wangcolor name="Dirt" color="#ff7700" tile="14" probability="1"/>
+ <wangtile tileid="0" wangid="0,1,0,2,0,1,0,1"/>
+ <wangtile tileid="1" wangid="0,1,0,2,0,2,0,1"/>
+ <wangtile tileid="2" wangid="0,1,0,1,0,2,0,1"/>
+ <wangtile tileid="3" wangid="0,4,0,1,0,4,0,4"/>
+ <wangtile tileid="4" wangid="0,4,0,4,0,1,0,4"/>
+ <wangtile tileid="5" wangid="0,1,0,4,0,1,0,1"/>
+ <wangtile tileid="6" wangid="0,1,0,4,0,4,0,1"/>
+ <wangtile tileid="7" wangid="0,1,0,1,0,4,0,1"/>
+ <wangtile tileid="8" wangid="0,2,0,2,0,1,0,1"/>
+ <wangtile tileid="9" wangid="0,2,0,2,0,2,0,2"/>
+ <wangtile tileid="10" wangid="0,1,0,1,0,2,0,2"/>
+ <wangtile tileid="11" wangid="0,1,0,4,0,4,0,4"/>
+ <wangtile tileid="12" wangid="0,4,0,4,0,4,0,1"/>
+ <wangtile tileid="13" wangid="0,4,0,4,0,1,0,1"/>
+ <wangtile tileid="14" wangid="0,4,0,4,0,4,0,4"/>
+ <wangtile tileid="15" wangid="0,1,0,1,0,4,0,4"/>
+ <wangtile tileid="16" wangid="0,2,0,1,0,1,0,1"/>
+ <wangtile tileid="17" wangid="0,2,0,1,0,1,0,2"/>
+ <wangtile tileid="18" wangid="0,1,0,1,0,1,0,2"/>
+ <wangtile tileid="19" wangid="0,2,0,1,0,2,0,2"/>
+ <wangtile tileid="20" wangid="0,2,0,2,0,1,0,2"/>
+ <wangtile tileid="21" wangid="0,4,0,1,0,1,0,1"/>
+ <wangtile tileid="22" wangid="0,4,0,1,0,1,0,4"/>
+ <wangtile tileid="23" wangid="0,1,0,1,0,1,0,4"/>
+ <wangtile tileid="24" wangid="0,1,0,3,0,1,0,1"/>
+ <wangtile tileid="25" wangid="0,1,0,3,0,3,0,1"/>
+ <wangtile tileid="26" wangid="0,1,0,1,0,3,0,1"/>
+ <wangtile tileid="27" wangid="0,1,0,2,0,2,0,2"/>
+ <wangtile tileid="28" wangid="0,2,0,2,0,2,0,1"/>
+ <wangtile tileid="29" wangid="0,1,0,1,0,1,0,1"/>
+ <wangtile tileid="30" wangid="0,1,0,1,0,1,0,1"/>
+ <wangtile tileid="31" wangid="0,1,0,1,0,1,0,1"/>
+ <wangtile tileid="32" wangid="0,3,0,3,0,1,0,1"/>
+ <wangtile tileid="33" wangid="0,3,0,3,0,3,0,3"/>
+ <wangtile tileid="34" wangid="0,1,0,1,0,3,0,3"/>
+ <wangtile tileid="35" wangid="0,3,0,1,0,3,0,3"/>
+ <wangtile tileid="36" wangid="0,3,0,3,0,1,0,3"/>
+ <wangtile tileid="37" wangid="0,1,0,1,0,1,0,1"/>
+ <wangtile tileid="38" wangid="0,1,0,1,0,1,0,1"/>
+ <wangtile tileid="39" wangid="0,1,0,1,0,1,0,1"/>
+ <wangtile tileid="40" wangid="0,3,0,1,0,1,0,1"/>
+ <wangtile tileid="41" wangid="0,3,0,1,0,1,0,3"/>
+ <wangtile tileid="42" wangid="0,1,0,1,0,1,0,3"/>
+ <wangtile tileid="43" wangid="0,1,0,3,0,3,0,3"/>
+ <wangtile tileid="44" wangid="0,3,0,3,0,3,0,1"/>
+ <wangtile tileid="45" wangid="0,1,0,1,0,1,0,1"/>
+ <wangtile tileid="46" wangid="0,1,0,1,0,1,0,1"/>
+ <wangtile tileid="47" wangid="0,1,0,1,0,1,0,1"/>
+ </wangset>"##,
+ 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)),
+ ]
+ }
+ );
+ }
+}