From 24a2956c2f166942073e08343daa434b344e2c5f Mon Sep 17 00:00:00 2001 From: Louis Capitanchik <contact@louiscap.co> Date: Tue, 17 Jan 2023 19:33:58 +0000 Subject: [PATCH] Wrap levels and layers, preprocess layers to extract tile info --- src/assets.rs | 14 ++-- src/lib.rs | 16 +++- src/map_query.rs | 207 ++++++++-------------------------------------- src/types.rs | 210 +++++++++++++++++++++++++++++++++++++++++++++++ src/utils.rs | 32 +++----- 5 files changed, 274 insertions(+), 205 deletions(-) create mode 100644 src/types.rs diff --git a/src/assets.rs b/src/assets.rs index e1d62ea..b9101ef 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -5,10 +5,11 @@ use anyhow::Error; use bevy::asset::{AssetEvent, AssetLoader, Assets, BoxedFuture, LoadContext, LoadedAsset}; use bevy::prelude::{EventReader, Res, ResMut, Resource}; use bevy::reflect::TypeUuid; -use ldtk_rust::{Level, Project, TilesetDefinition}; +use ldtk_rust::Project; use serde_json::Value; use crate::utils::SerdeClone; +use crate::LdtkLevel; #[derive(TypeUuid)] #[uuid = "292a8918-9487-11ed-8dd2-43b6f36cb076"] @@ -51,9 +52,9 @@ impl AssetLoader for LdtkLoader { } #[derive(Resource, Default)] -pub struct LevelIndex(pub HashMap<String, Level>); +pub struct LevelIndex(pub HashMap<String, LdtkLevel>); impl Deref for LevelIndex { - type Target = HashMap<String, Level>; + type Target = HashMap<String, LdtkLevel>; fn deref(&self) -> &Self::Target { &self.0 } @@ -92,7 +93,10 @@ pub fn handle_ldtk_project_events( AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { if let Some(LdtkProject(project)) = assets.get(handle) { for level in &project.levels { - level_index.insert(level.identifier.clone(), level.serde_clone()); + level_index.insert( + level.identifier.clone(), + LdtkLevel::from(level.serde_clone()), + ); } for tileset in &project.defs.tilesets { @@ -100,7 +104,7 @@ pub fn handle_ldtk_project_events( for custom in &tileset.custom_data { tile_meta.insert( custom.tile_id, - serde_json::from_str(&*custom.data).unwrap(), + serde_json::from_str(&custom.data).unwrap(), ); } tilset_index.insert(tileset.identifier.clone(), TileMetadata(tile_meta)); diff --git a/src/lib.rs b/src/lib.rs index fbadc38..53bb4f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ mod assets; mod camera; mod locator; mod map_query; +mod types; pub(crate) mod utils; // mod spawning; @@ -45,6 +46,13 @@ mod __plugin { } } } + impl<CameraFilter: ReadOnlyWorldQuery + Send + Sync + 'static> Default + for MicroLDTKCameraPlugin<CameraFilter> + { + fn default() -> Self { + Self::new() + } + } pub struct MicroLDTKCameraPlugin<CameraFilter: ReadOnlyWorldQuery> { _p: PhantomData<CameraFilter>, @@ -65,7 +73,7 @@ use std::sync::atomic::{AtomicU32, Ordering}; pub use __plugin::{MicroLDTKCameraPlugin, MicroLDTKPlugin}; pub use assets::{LdtkLoader, LdtkProject, LevelIndex, TileMetadata, TilesetIndex}; -pub use map_query::{CameraBounds, LayerRef, MapQuery, TileRef}; -pub use utils::{ - entity_centre, entity_to_worldspace, grid_to_px, px_to_grid, ActiveLevel, WorldLinked, -}; +pub use camera::CameraBounder; +pub use map_query::{CameraBounds, MapQuery}; +pub use types::{LdtkLayer, LdtkLevel, SpatialIndex, TileFlip, TileRef}; +pub use utils::{entity_centre, grid_to_px, px_to_grid, ActiveLevel, WorldLinked}; diff --git a/src/map_query.rs b/src/map_query.rs index 2238a41..6ad34d7 100644 --- a/src/map_query.rs +++ b/src/map_query.rs @@ -1,85 +1,23 @@ -use std::fmt::{Debug, Formatter}; +use std::fmt::Debug; use std::marker::PhantomData; -use std::path::Path; use std::str::FromStr; use bevy::ecs::system::SystemParam; use bevy::prelude::*; -use ldtk_rust::{EntityInstance, LayerInstance, Level, TileInstance}; -use num_traits::AsPrimitive; +use ldtk_rust::EntityInstance; -// use crate::assets::level_index::LevelIndex; -use crate::assets::{LdtkProject, LevelIndex}; -use crate::utils::{ActiveLevel, Indexer, SerdeClone}; -use crate::{get_ldtk_tile_scale, px_to_grid}; +use crate::assets::LevelIndex; +use crate::utils::{ActiveLevel, SerdeClone}; +use crate::{get_ldtk_tile_scale, LdtkLayer, LdtkLevel}; #[derive(SystemParam)] pub struct MapQuery<'w, 's> { - assets: Res<'w, Assets<LdtkProject>>, active: Option<Res<'w, ActiveLevel>>, index: Res<'w, LevelIndex>, #[system_param(ignore)] _e: PhantomData<&'s ()>, } -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() - } -} - pub struct InstanceRef<'a> { pub entity: &'a EntityInstance, } @@ -98,7 +36,7 @@ impl<'a> InstanceRef<'a> { return field .value .as_ref() - .map(|v| v.clone()) + .cloned() .unwrap_or(serde_json::Value::Null); } } @@ -107,77 +45,6 @@ impl<'a> InstanceRef<'a> { } } -pub struct LayerRef<'a> { - pub idx: usize, - pub indexer: Indexer, - pub layer: &'a LayerInstance, -} - -impl<'a> LayerRef<'a> { - pub fn new(idx: usize, indexer: Indexer, layer: &'a LayerInstance) -> Self { - LayerRef { - layer, - indexer, - idx, - } - } - 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.layer - .grid_tiles - .iter() - .chain(self.layer.auto_layer_tiles.iter()) - .for_each(|tile: &TileInstance| { - let (x, y) = match tile.px.as_slice() { - &[x, y] => (px_to_grid(x), px_to_grid(y)), - _ => { - return; - } - }; - - cb(x, y, TileRef::new(tile)); - }); - } - pub fn get_z_delta(&self) -> f32 { - (self.idx as f32) / 100.0 - } - - pub fn get_tile_at( - &self, - x: impl AsPrimitive<isize>, - y: impl AsPrimitive<isize>, - ) -> Option<TileRef> { - let idx = self.indexer.index(x, y); - - match self.layer.grid_tiles.is_empty() { - true => self - .layer - .auto_layer_tiles - .get(idx) - .map(|tile| TileRef::new(tile)), - false => self - .layer - .grid_tiles - .get(idx) - .map(|tile| TileRef::new(tile)), - } - } - - /// 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) - }) - } -} - #[derive(Copy, Clone, Debug)] pub struct CameraBounds { pub left: f32, @@ -206,24 +73,15 @@ impl<'w, 's> MapQuery<'w, 's> { // --- than the currently active one. 'active' methods are a convenience to // --- call the static accessors on whatever the current level is - pub fn get_indexer_for(level: &Level) -> Indexer { - Indexer::new(px_to_grid(level.px_wid), px_to_grid(level.px_hei)) - } - - pub fn for_each_layer_of(level: &Level, mut cb: impl FnMut(LayerRef)) { - if let Some(layers) = level.layer_instances.as_ref() { - for layer in layers.iter().rev().enumerate() { - cb(LayerRef::new( - layer.0, - MapQuery::get_indexer_for(level), - layer.1, - )); - } + pub fn for_each_layer_of(level: &LdtkLevel, mut cb: impl FnMut(&LdtkLayer)) { + for layer in level.layers().rev() { + cb(layer); } } - pub fn get_entities_of(level: &Level) -> Vec<&EntityInstance> { + pub fn get_entities_of(level: &LdtkLevel) -> Vec<&EntityInstance> { level + .level_ref() .layer_instances .as_ref() .map(|layers| { @@ -235,8 +93,9 @@ impl<'w, 's> MapQuery<'w, 's> { .unwrap_or_default() } - pub fn get_instance_refs_of(level: &Level) -> Vec<InstanceRef> { + pub fn get_instance_refs_of(level: &LdtkLevel) -> Vec<InstanceRef> { level + .level_ref() .layer_instances .as_ref() .map(|layers| { @@ -250,34 +109,32 @@ impl<'w, 's> MapQuery<'w, 's> { } pub fn get_filtered_entities_of( - level: &Level, + level: &LdtkLevel, entity_type: impl ToString, ) -> Vec<&EntityInstance> { let e_type = entity_type.to_string(); - match level.layer_instances.as_ref() { - Some(inst) => inst - .iter() - .flat_map(|layer| layer.entity_instances.iter()) - .filter(|inst| inst.identifier == e_type) - .collect(), - None => Vec::new(), - } + level + .layers() + .flat_map(|layer| layer.as_ref().entity_instances.iter()) + .filter(|inst| inst.identifier == e_type) + .collect() } - pub fn get_owned_entities_of(level: &Level) -> Vec<EntityInstance> { + pub fn get_owned_entities_of(level: &LdtkLevel) -> Vec<EntityInstance> { level - .layer_instances - .as_ref() - .map(|layers| { - layers + .layers() + .flat_map(|layer| { + layer + .as_ref() + .entity_instances .iter() - .flat_map(|layer| layer.entity_instances.iter().map(|inst| inst.serde_clone())) - .collect() + .map(|inst| inst.serde_clone()) }) - .unwrap_or_default() + .collect() } - pub fn get_camera_bounds_of(level: &Level) -> CameraBounds { + pub fn get_camera_bounds_of(level: &LdtkLevel) -> CameraBounds { + let level = level.level_ref(); CameraBounds { left: 0.0, top: level.px_hei as f32, @@ -286,7 +143,7 @@ impl<'w, 's> MapQuery<'w, 's> { } } - pub fn get_active_level(&self) -> Option<&Level> { + pub fn get_active_level(&self) -> Option<&LdtkLevel> { self.active .as_ref() .and_then(|index| self.index.get(&index.map)) @@ -294,7 +151,7 @@ impl<'w, 's> MapQuery<'w, 's> { pub fn get_entities(&self) -> Vec<&EntityInstance> { self.get_active_level() - .map(|level| MapQuery::get_entities_of(level)) + .map(MapQuery::get_entities_of) .unwrap_or_default() } @@ -302,7 +159,7 @@ impl<'w, 's> MapQuery<'w, 's> { self.get_active_level().map(MapQuery::get_camera_bounds_of) } - pub fn for_each_layer(&self, mut cb: impl FnMut(LayerRef)) { + pub fn for_each_layer(&self, cb: impl FnMut(&LdtkLayer)) { if let Some(level) = self.get_active_level() { Self::for_each_layer_of(level, cb); } diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..7fa9a4b --- /dev/null +++ b/src/types.rs @@ -0,0 +1,210 @@ +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 + } +} diff --git a/src/utils.rs b/src/utils.rs index 3608636..76e3674 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -15,7 +15,7 @@ where T: Serialize + DeserializeOwned, { fn serde_clone(&self) -> Self { - serde_json::from_value(serde_json::to_value(&self).unwrap()).unwrap() + serde_json::from_value(serde_json::to_value(self).unwrap()).unwrap() } } @@ -27,19 +27,9 @@ pub fn grid_to_px<T: AsPrimitive<f32>>(t: T) -> f32 { t.as_() * get_ldtk_tile_scale() } -pub fn entity_to_worldspace(level_height: i64, entity: &EntityInstance) -> (f32, f32) { - let centre_align_pixel_x = grid_to_px(entity.grid[0]) - (get_ldtk_tile_scale() / 2.0); - let centre_align_pixel_y = grid_to_px(entity.grid[1]) - (get_ldtk_tile_scale() / 2.0); - let inverted_pixel_y = level_height as f32 - centre_align_pixel_y - get_ldtk_tile_scale(); - let box_aligned_x = centre_align_pixel_x + (entity.width / 2) as f32; - let box_aligned_y = inverted_pixel_y - (entity.height / 2) as f32; - - (box_aligned_x, box_aligned_y) -} - pub fn entity_centre(level_height: i64, entity: &EntityInstance) -> (f32, f32) { let x = entity.px[0] - (entity.width / 2); - let y = (level_height - entity.px[1] - entity.height / 2); + let y = level_height - entity.px[1] - entity.height / 2; (x as f32, y as f32) } @@ -63,33 +53,33 @@ impl ActiveLevel { #[derive(Debug, Copy, Clone)] pub struct Indexer { - width: isize, - height: isize, + width: i64, + height: i64, } impl Indexer { - pub fn new(width: impl AsPrimitive<isize>, height: impl AsPrimitive<isize>) -> Self { + pub fn new(width: impl AsPrimitive<i64>, height: impl AsPrimitive<i64>) -> Self { Self { width: width.as_(), height: height.as_(), } } - pub fn index(&self, x: impl AsPrimitive<isize>, y: impl AsPrimitive<isize>) -> usize { + pub fn index(&self, x: impl AsPrimitive<i64>, y: impl AsPrimitive<i64>) -> usize { ((y.as_() * self.width) + x.as_()).as_() } - pub fn reverse(&self, index: impl AsPrimitive<isize>) -> (usize, usize) { + pub fn reverse(&self, index: impl AsPrimitive<i64>) -> (usize, usize) { ( - (index.as_() % self.width) as usize, - (index.as_() / self.width) as usize, + (index.as_() % self.width).max(0) as usize, + (index.as_() / self.width).max(0) as usize, ) } - pub fn width(&self) -> isize { + pub fn width(&self) -> i64 { self.width } - pub fn height(&self) -> isize { + pub fn height(&self) -> i64 { self.height } } -- GitLab