use std::fmt::{Debug, Formatter}; use std::path::Path; use bevy::math::{IVec2, UVec2}; use bevy::utils::HashMap; use ldtk_rust::{LayerInstance, Level, TileInstance}; use num_traits::AsPrimitive; use crate::utils::{Indexer, SerdeClone}; use crate::{get_ldtk_tile_scale, px_to_grid}; pub struct TileRef<'a> { pub tile: &'a TileInstance, } #[repr(C)] #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default, Debug)] pub enum TileFlip { #[default] None = 0, Horizontal = 1, Vertical = 2, Both = 3, } impl TileFlip { pub fn is_horizontal(&self) -> bool { match self { Self::None | Self::Vertical => false, Self::Horizontal | Self::Both => true, } } pub fn is_vertical(&self) -> bool { match self { Self::None | Self::Horizontal => false, Self::Vertical | Self::Both => true, } } } impl<'a> TileRef<'a> { pub fn new(tile: &'a TileInstance) -> Self { TileRef { tile } } pub fn gid(&self) -> usize { (self.tile.src[0] * self.tile.src[1]).unsigned_abs() as usize } pub fn get_flip(&self) -> TileFlip { match self.tile.f { 1 => TileFlip::Horizontal, 2 => TileFlip::Vertical, 3 => TileFlip::Both, _ => TileFlip::None, } } } impl<'a> Debug for TileRef<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("TileRef") .field("gid", &self.gid()) .field("tile.t", &self.tile.t) .field("tile.d", &self.tile.d) .field("tile.px", &self.tile.px) .field("tile.src", &self.tile.src) .finish() } } #[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Copy, Hash, Debug)] pub struct SpatialIndex(i64, i64); impl<A, B> From<(A, B)> for SpatialIndex where A: AsPrimitive<i64>, B: AsPrimitive<i64>, { fn from(value: (A, B)) -> Self { Self(value.0.as_(), value.1.as_()) } } impl From<UVec2> for SpatialIndex { fn from(value: UVec2) -> Self { Self(value.x as i64, value.y as i64) } } impl From<IVec2> for SpatialIndex { fn from(value: IVec2) -> Self { Self(value.x as i64, value.y as i64) } } pub struct LdtkLevel { level: Level, processed_layers: Vec<LdtkLayer>, } impl LdtkLevel { pub fn level_ref(&self) -> &Level { &self.level } pub fn level_ref_mut(&mut self) -> &mut Level { &mut self.level } pub fn layers(&self) -> impl DoubleEndedIterator<Item = &LdtkLayer> { self.processed_layers.iter() } pub fn layers_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut LdtkLayer> { self.processed_layers.iter_mut() } pub fn get_indexer(&self) -> Indexer { Indexer::new(px_to_grid(self.level.px_wid), px_to_grid(self.level.px_hei)) } } impl From<Level> for LdtkLevel { fn from(mut value: Level) -> Self { let layers = value.layer_instances.take(); Self { processed_layers: layers .unwrap_or_default() .into_iter() .enumerate() .map(|(idx, inst)| LdtkLayer::new(idx, inst)) .collect(), level: value, } } } pub struct LdtkLayer { layer: LayerInstance, position_lookup: HashMap<SpatialIndex, TileInstance>, level: usize, indexer: Indexer, } impl LdtkLayer { pub fn new(index: usize, layer: LayerInstance) -> Self { let tile_list = layer.grid_tiles.iter().chain(&layer.auto_layer_tiles); let mut position_lookup = HashMap::with_capacity(layer.grid_tiles.len() + layer.auto_layer_tiles.len()); let scale = get_ldtk_tile_scale() as i64; let indexer = Indexer::new(layer.c_wid, layer.c_hei); for tile in tile_list { let x = tile.px[0] / scale; let y = tile.px[1] / scale; position_lookup.insert((x, y).into(), tile.serde_clone()); } Self { level: index, indexer, layer, position_lookup, } } pub fn indexer(&self) -> Indexer { self.indexer } pub fn has_tiles(&self) -> bool { !(self.layer.auto_layer_tiles.is_empty() && self.layer.grid_tiles.is_empty()) } pub fn for_each_tile(&self, mut cb: impl FnMut(i64, i64, TileRef)) { self.position_lookup.iter().for_each(|(pos, tile)| { cb(pos.0, pos.1, TileRef::new(tile)); }); } pub fn get_z_delta(&self) -> f32 { (self.level as f32) / 100.0 } pub fn get_tile(&self, pos: impl Into<SpatialIndex>) -> Option<TileRef> { self.position_lookup.get(&pos.into()).map(TileRef::new) } pub fn get_tile_at( &self, a: impl AsPrimitive<i64>, b: impl AsPrimitive<i64>, ) -> Option<TileRef> { self.position_lookup .get(&SpatialIndex(a.as_(), b.as_())) .map(TileRef::new) } /// Returns the inferred name of the tileset used for this layer. This is assumed to be the /// name of the tileset file, without the preceding path segments or the file extension. Case /// remains unchanged pub fn infer_tileset_name(&self) -> Option<String> { self.layer.tileset_rel_path.as_ref().and_then(|path| { Path::new(path) .file_stem() .and_then(|stem| stem.to_str()) .map(String::from) }) } } impl AsRef<LayerInstance> for LdtkLayer { fn as_ref(&self) -> &LayerInstance { &self.layer } }