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)),
]
}
);
}
}