diff --git a/Cargo.lock b/Cargo.lock index cbcf274a7251610986854b5185d1474c0dd9fba1..6b553a728cd5f9247dde3213b097a4195c83f013 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,12 +132,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anyhow" -version = "1.0.97" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" - [[package]] name = "approx" version = "0.5.1" @@ -2235,17 +2229,16 @@ dependencies = [ [[package]] name = "micro_ldtk" -version = "0.14.0" +version = "0.14.1" dependencies = [ - "anyhow", "bevy", "glam", - "log", "micro_autotile", "num-traits", "quadtree_rs", "serde", "serde_json", + "test-case", "thiserror 2.0.12", ] @@ -3379,6 +3372,39 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2 1.0.94", + "quote 1.0.40", + "syn 2.0.100", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2 1.0.94", + "quote 1.0.40", + "syn 2.0.100", + "test-case-core", +] + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index f667095a1606f8aa8dddaa08b8303bd3044d30a7..7ab6a1b629b576a4d14a7213b41c5ce20c891441 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "micro_ldtk" -version = "0.14.0" +version = "0.14.1" edition = "2021" authors = [ @@ -12,10 +12,10 @@ license = "Apache-2.0" [features] default = ["ldtk_1_5_3", "autotile"] -ldtk_1_5_3 = ["_supports_ldtk", "_supports_intgridgroup", "_optional_tile_list"] -ldtk_1_4_1 = ["_supports_ldtk", "_supports_intgridgroup"] -ldtk_1_4_0 = ["_supports_ldtk", "_supports_intgridgroup"] -ldtk_1_3_0 = ["_supports_ldtk"] +ldtk_1_5_3 = ["_supports_ldtk", "_supports_intgridgroup", "_optional_tile_list", "_supports_ui_tags", "_supports_worlds"] +ldtk_1_4_1 = ["_supports_ldtk", "_supports_intgridgroup", "_supports_worlds"] +ldtk_1_4_0 = ["_supports_ldtk", "_supports_intgridgroup", "_supports_worlds"] +ldtk_1_3_0 = ["_supports_ldtk", "_supports_worlds"] ldtk_1_2_5 = ["_supports_ldtk"] ldtk_1_2_4 = ["_supports_ldtk"] ldtk_1_2_3 = ["_supports_ldtk"] @@ -29,17 +29,17 @@ ldtk_1_1_0 = ["_supports_ldtk"] ldtk_1_0_0 = ["_supports_ldtk"] autotile = ["dep:micro_autotile"] bevy = ["dep:bevy"] +_supports_ui_tags = [] _supports_intgridgroup = [] _supports_ldtk = [] +_supports_worlds = [] _optional_tile_list = [] no_panic = [] [dependencies] bevy = { optional = true, version = "0.15", default-features = false, features = ["bevy_render", "bevy_sprite", "bevy_asset", "serialize"] } -anyhow = "1.0" thiserror = "2.0" -log = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" num-traits = "0.2" @@ -47,3 +47,5 @@ quadtree_rs = "0.1" micro_autotile = { version = "0.2", optional = true } glam = { version = "0.29", features = ["serde"] } +[dev-dependencies] +test-case = "3.3.1" \ No newline at end of file diff --git a/src/ldtk/mod.rs b/src/ldtk/mod.rs index 09f4471204cc46e67bde6fe7c9c382bd51414466..53aa94e01d45273d1ba04bccfe0aed340928c492 100644 --- a/src/ldtk/mod.rs +++ b/src/ldtk/mod.rs @@ -224,24 +224,12 @@ impl Project { } } - #[cfg(any( - feature = "ldtk_1_2_5", - feature = "ldtk_1_2_4", - feature = "ldtk_1_2_3", - feature = "ldtk_1_2_2", - feature = "ldtk_1_2_1", - feature = "ldtk_1_2_0", - feature = "ldtk_1_1_3", - feature = "ldtk_1_1_2", - feature = "ldtk_1_1_1", - feature = "ldtk_1_1_0", - feature = "ldtk_1_0_0" - ))] + #[cfg(not(feature = "_supports_worlds"))] pub fn get_world_levels(&self, identifier: impl ToString) -> Vec<&Level> { vec![] } - #[cfg(any(feature = "ldtk_1_3_0", feature = "ldtk_1_4_0", feature = "ldtk_1_4_1"))] + #[cfg(feature = "_supports_worlds")] pub fn get_world_levels(&self, identifier: impl ToString) -> Vec<&Level> { let id = identifier.to_string(); self.worlds @@ -322,9 +310,12 @@ mod autotile_support { fn from(value: &Project) -> Self { let mut base_set = AutoRuleSet::default(); - for layers in value.defs.layers.iter() { - for rule_group in layers.auto_rule_groups.iter() { - base_set = base_set + rule_group.into(); + #[cfg(feature = "_supports_ui_tags")] + { + for layers in value.defs.layers.iter() { + for rule_group in layers.auto_rule_groups.iter() { + base_set = base_set + rule_group.into(); + } } } diff --git a/src/system/types.rs b/src/system/types.rs index 5bd656887a193d6c312eb7a8a4f67fd443ba7ad5..b7497b8ed29bbc22584a28c225d57fc934fadc38 100644 --- a/src/system/types.rs +++ b/src/system/types.rs @@ -119,13 +119,16 @@ pub struct LdtkLevel { impl LdtkLevel { pub fn from_project(project: &Project, level: Level) -> Self { let mut level_data = LdtkLevel::from(level); - level_data.layers_mut().for_each(|layer| { - if let Some(def) = project.defs.layers.iter().find(|inner| { - inner.uid == layer.layer.layer_def_uid - }) { - layer.tags = def.ui_filter_tags.clone(); - } - }); + #[cfg(feature = "_supports_ui_tags")] + { + level_data.layers_mut().for_each(|layer| { + if let Some(def) = project.defs.layers.iter().find(|inner| { + inner.uid == layer.layer.layer_def_uid + }) { + layer.tags = def.ui_filter_tags.clone(); + } + }); + } level_data } pub fn width(&self) -> f32 { diff --git a/src/system/utils.rs b/src/system/utils.rs index be47c5e2a84b6e62363713e43015222cbbc6700b..0b03580a841275a25c0489f9d02d34c3a43c5577 100644 --- a/src/system/utils.rs +++ b/src/system/utils.rs @@ -1,5 +1,6 @@ #[cfg(feature = "bevy")] -use bevy::prelude::{Component, IVec2, Resource}; +use bevy::prelude::{Component, Resource}; +use glam::IVec2; use num_traits::AsPrimitive; use crate::get_ldtk_tile_scale; @@ -19,11 +20,11 @@ pub fn entity_centre(level_height: i64, entity: &EntityInstance) -> (f32, f32) { (x as f32, y as f32) } -#[cfg_attr(feature="bevy", derive(Component))] +#[cfg_attr(feature = "bevy", derive(Component))] pub struct WorldLinked; #[derive(Default, Clone, Debug)] -#[cfg_attr(feature="bevy", derive(Resource))] +#[cfg_attr(feature = "bevy", derive(Resource))] pub struct ActiveLevel { pub map: String, pub dirty: bool, @@ -53,7 +54,10 @@ impl Indexer { } pub fn index(&self, x: impl AsPrimitive<i64>, y: impl AsPrimitive<i64>) -> usize { - ((y.as_() * self.width) + x.as_()).as_() + match (self.width(), self.height()) { + (0, _) | (_, 0) => 0, + (w, _) => ((y.as_() * w) + x.as_()).as_(), + } } pub fn index_checked( @@ -69,10 +73,14 @@ impl Indexer { } pub fn reverse(&self, index: impl AsPrimitive<i64>) -> (usize, usize) { - ( - (index.as_() % self.width).max(0) as usize, - (index.as_() / self.width).max(0) as usize, - ) + let index = index.as_(); + match index { + 0 => (0, 0), + _ => ( + index.checked_rem(self.width).unwrap_or(0) as usize, + index.checked_div(self.width).unwrap_or(0) as usize, + ) + } } pub fn width(&self) -> i64 { @@ -89,7 +97,6 @@ impl Indexer { x >= 0 && x < self.width && y >= 0 && y < self.height } - #[cfg(feature = "bevy")] /// Perform a transformation to flip a grid point (top down coordinates) into a render /// point (bottom up coordinates) pub fn flip_y(&self, point: IVec2) -> IVec2 { diff --git a/tests/indexer.rs b/tests/indexer.rs new file mode 100644 index 0000000000000000000000000000000000000000..c20979ef41500c10fe5346e5955e25b1e03e8d39 --- /dev/null +++ b/tests/indexer.rs @@ -0,0 +1,88 @@ +use micro_ldtk::Indexer; +use glam::IVec2; +use num_traits::AsPrimitive; +use test_case::test_case; + +#[test_case(0, 0 => 0)] +#[test_case(4, 0 => 4)] +#[test_case(0, 3 => 15)] +#[test_case(4, 3 => 19)] +#[test_case(2, 2 => 12)] +#[test_case(1i32, 1i32 => 6)] +#[test_case(2.5f32, 1.2f32 => 7)] // Floating point coordinates are truncated +fn test_indexer_calculates_correct_index(x: impl AsPrimitive<i64>, y: impl AsPrimitive<i64>) -> usize { + let indexer = Indexer::new(5, 4); + indexer.index(x, y) +} + +#[test_case(0 => (0, 0))] +#[test_case(4 => (4, 0))] +#[test_case(5 => (0, 1))] +#[test_case(9 => (4, 1))] +#[test_case(10 => (0, 2))] +#[test_case(14 => (4, 2))] +fn test_indexer_reverse(index: usize) -> (usize, usize) { + let indexer = Indexer::new(5, 3); + indexer.reverse(index) +} + +#[test_case(0, 0)] +#[test_case(4, 0)] +#[test_case(0, 1)] +#[test_case(2, 2)] +#[test_case(4, 2)] +fn test_index_is_reflexive(x: usize, y: usize) { + let indexer = Indexer::new(5, 3); + let idx = indexer.index(x, y); + let (x_rev, y_rev) = indexer.reverse(idx); + + assert_eq!((x, y), (x_rev, y_rev)); +} + +#[test_case(5, -1)] +#[test_case(5, 10)] +#[test_case(10, 10)] +#[test_case(10, 3)] +#[test_case(-12, 3)] +#[test_case(-00, 123_000_000)] +fn test_indexer_index_checked_returns_none_for_out_of_bounds(x: i64, y: i64) { + let indexer = Indexer::new(10, 10); + assert_eq!(indexer.index_checked(x, y), None); +} + + +#[test] +fn test_indexer_with_zero_dimensions() { + + // Create an indexer with zero width + let zero_width_indexer = Indexer::new(0, 10); + + assert_eq!(zero_width_indexer.width(), 0); + assert_eq!(zero_width_indexer.height(), 10); + + // Zero width should make all x coordinates invalid + assert!(!zero_width_indexer.is_valid(0, 5)); + assert_eq!(zero_width_indexer.index_checked(0, 5), None); + + // Index calculation with zero width + assert_eq!(zero_width_indexer.index(2, 3), 0); // 3 * 0 + 2 = 0 + + // Reverse calculation with zero width + let (x, y) = zero_width_indexer.reverse(5); + assert_eq!(x, 0); // Any index % 0 is treated as 0 due to .max(0) + assert_eq!(y, 0); // Any index / 0 is treated as 0 due to .max(0) + + // Create an indexer with zero height + let zero_height_indexer = Indexer::new(10, 0); + assert_eq!(zero_height_indexer.width(), 10); + assert_eq!(zero_height_indexer.height(), 0); + + // Zero height should make all y coordinates invalid + assert!(!zero_height_indexer.is_valid(5, 0)); + assert_eq!(zero_height_indexer.index_checked(5, 0), None); + + // Test flip_y with zero height + let flipped = zero_height_indexer.flip_y(IVec2::new(5, 3)); + assert_eq!(flipped, IVec2::new(5, -3)); // 0 - 3 = -3 +} +