From 0975cb46c79080e272e9fcb4c35c74d7c9777af2 Mon Sep 17 00:00:00 2001 From: Alejandro Perea <alexpro820@gmail.com> Date: Mon, 25 Apr 2022 21:37:02 +0200 Subject: [PATCH] Add basic chunk utilities to infinite layers (#210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add basic chunk utils to infinite layers * Mark TODOs * Apply suggestions from code review Co-authored-by: Thorbjørn Lindeijer <bjorn@lindeijer.nl> * Apply interface suggestions from code review * Update changelog Co-authored-by: Thorbjørn Lindeijer <bjorn@lindeijer.nl> --- CHANGELOG.md | 5 ++ src/layers/tile/infinite.rs | 147 ++++++++++++++++++++++++++++++++---- tests/lib.rs | 2 +- 3 files changed, 138 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50ffa3a..fe131d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.10.2] ### Added +- Map-wrapped chunks: `ChunkWrapper`. +- Ability to access infinite tile layer chunks via `InfiniteTileLayer::chunks`, +`InfiniteTileLayerData::chunk_data`, `InfiniteTileLayerData::get_chunk_data` & +`InfiniteTileLayer::get_chunk`, as well as chunk data via `Chunk::get_tile_data` & +`ChunkWrapper::get_tile`. - `TileLayer::width` & `TileLayer::height` for ergonomic access of width/height. - `FiniteTileLayerData::get_tile_data`, `InfiniteTileLayerData::get_tile_data`. - `Default` derived implementation for `Loader` & `FilesystemResourceCache` diff --git a/src/layers/tile/infinite.rs b/src/layers/tile/infinite.rs index 01b5bb8..9c79e5b 100644 --- a/src/layers/tile/infinite.rs +++ b/src/layers/tile/infinite.rs @@ -41,7 +41,7 @@ impl InfiniteTileLayerData { let chunk = InternalChunk::new(parser, attrs, e.clone(), c.clone(), tilesets)?; for x in chunk.x..chunk.x + chunk.width as i32 { for y in chunk.y..chunk.y + chunk.height as i32 { - let chunk_pos = tile_to_chunk_pos(x, y); + let chunk_pos = Chunk::tile_to_chunk_pos(x, y); let relative_pos = (x - chunk_pos.0 * Chunk::WIDTH as i32, y - chunk_pos.1 * Chunk::HEIGHT as i32); let chunk_index = (relative_pos.0 + relative_pos.1 * Chunk::WIDTH as i32) as usize; let internal_pos = (x - chunk.x, y - chunk.y); @@ -63,7 +63,7 @@ impl InfiniteTileLayerData { /// /// If you want to get a [`Tile`](`crate::Tile`) instead, use [`InfiniteTileLayer::get_tile()`]. pub fn get_tile_data(&self, x: i32, y: i32) -> Option<&LayerTileData> { - let chunk_pos = tile_to_chunk_pos(x, y); + let chunk_pos = Chunk::tile_to_chunk_pos(x, y); self.chunks .get(&chunk_pos) .and_then(|chunk| { @@ -76,30 +76,51 @@ impl InfiniteTileLayerData { }) .flatten() } -} -fn tile_to_chunk_pos(x: i32, y: i32) -> (i32, i32) { - ( - floor_div(x, Chunk::WIDTH as i32), - floor_div(y, Chunk::HEIGHT as i32), - ) + /// Returns an iterator over only the data part of the chunks of this tile layer. + /// + /// In 99.99% of cases you'll want to use [`InfiniteTileLayer::chunks()`] instead; Using this method is only + /// needed if you *only* require the tile data of the chunks (and no other utilities provided by + /// the map-wrapped [`LayerTile`]), and you are in dire need for that extra bit of performance. + /// + /// This iterator doesn't have any particular order. + #[inline] + pub fn chunk_data(&self) -> impl ExactSizeIterator<Item = ((i32, i32), &Chunk)> { + self.chunks.iter().map(|(pos, chunk)| (*pos, chunk)) + } + + /// Obtains a chunk's data by its position. To obtain the position of the chunk that contains a + /// tile, use [Chunk::tile_to_chunk_pos()]. + /// + /// In 99.99% of cases you'll want to use [`InfiniteTileLayer::get_chunk()`] instead; Using this method is only + /// needed if you *only* require the tile data of the chunk (and no other utilities provided by + /// the map-wrapped [`LayerTile`]), and you are in dire need for that extra bit of performance. + #[inline] + pub fn get_chunk_data(&self, x: i32, y: i32) -> Option<&Chunk> { + self.chunks.get(&(x, y)) + } } -/// Part of an infinite tile layer. +/// Part of an infinite tile layer's data. +/// +/// Has only the tile data contained within and not a reference to the map it is part of. It is for +/// this reason that this type should actually be called `ChunkData`, and so this will change in +/// the next breaking release. In 99.99% of cases you'll actually want to use [`ChunkWrapper`]. +// TODO(0.11.0): Rename to ChunkData #[derive(Debug, PartialEq, Clone)] pub struct Chunk { tiles: Box<[Option<LayerTileData>; Self::TILE_COUNT]>, } impl Chunk { - /// Internal infinite layer chunk width. Do not rely on this value as it might change between - /// versions. + /// Infinite layer chunk width. This constant might change between versions, not counting as a + /// breaking change. pub const WIDTH: u32 = 16; - /// Internal infinite layer chunk height. Do not rely on this value as it might change between - /// versions. + /// Infinite layer chunk height. This constant might change between versions, not counting as a + /// breaking change. pub const HEIGHT: u32 = 16; - /// Internal infinite layer chunk tile count. Do not rely on this value as it might change - /// between versions. + /// Infinite layer chunk tile count. This constant might change between versions, not counting + /// as a breaking change. pub const TILE_COUNT: usize = Self::WIDTH as usize * Self::HEIGHT as usize; pub(crate) fn new() -> Self { @@ -107,6 +128,45 @@ impl Chunk { tiles: Box::new([None; Self::TILE_COUNT]), } } + + /// Obtains the tile data present at the position given relative to the chunk's top-left-most tile. + /// + /// If the position given is invalid or the position is empty, this function will return [`None`]. + /// + /// If you want to get a [`LayerTile`](`crate::LayerTile`) instead, use [`ChunkWrapper::get_tile()`]. + pub fn get_tile_data(&self, x: i32, y: i32) -> Option<&LayerTileData> { + if x < Self::WIDTH as i32 && y < Self::HEIGHT as i32 && x >= 0 && y >= 0 { + self.tiles[x as usize + y as usize * Self::WIDTH as usize].as_ref() + } else { + None + } + } + + /// Returns the position of the chunk that contains the given tile position. + pub fn tile_to_chunk_pos(x: i32, y: i32) -> (i32, i32) { + ( + floor_div(x, Chunk::WIDTH as i32), + floor_div(y, Chunk::HEIGHT as i32), + ) + } +} + +// TODO(0.11.0): Rename to Chunk +map_wrapper!( + #[doc = "Part of an [`InfiniteTileLayer`]."] + #[doc = "This is the only map-wrapped type that has a `Wrapper` suffix. This will change in a later version."] + ChunkWrapper => Chunk +); + +impl<'map> ChunkWrapper<'map> { + /// Obtains the tile present at the position given relative to the chunk's top-left-most tile. + /// + /// If the position given is invalid or the position is empty, this function will return [`None`]. + pub fn get_tile(&self, x: i32, y: i32) -> Option<LayerTile<'map>> { + self.data + .get_tile_data(x, y) + .map(|data| LayerTile::new(self.map(), data)) + } } #[derive(Debug, PartialEq, Clone)] @@ -167,4 +227,61 @@ impl<'map> InfiniteTileLayer<'map> { .get_tile_data(x, y) .map(|data| LayerTile::new(self.map, data)) } + + /// Returns an iterator over different parts of this map called [`Chunk`]s. + /// + /// These **may not** correspond with the chunks in the TMX file, as the chunk size is + /// implementation defined (see [`Chunk::WIDTH`], [`Chunk::HEIGHT`]). + /// + /// The iterator item contains the position of the chunk in chunk coordinates along with a + /// reference to the actual chunk at that position. + /// + /// This iterator doesn't have any particular order. + /// + /// ## Example + /// ``` + /// # use tiled::{Loader, LayerType, TileLayer}; + /// use tiled::Chunk; + /// + /// # let map = Loader::new() + /// # .load_tmx_map("assets/tiled_base64_zlib_infinite.tmx") + /// # .unwrap(); + /// # if let LayerType::TileLayer(TileLayer::Infinite(infinite_layer)) = + /// # &map.get_layer(0).unwrap().layer_type() + /// # { + /// for (chunk_pos, chunk) in infinite_layer.chunks() { + /// for x in 0..Chunk::WIDTH as i32 { + /// for y in 0..Chunk::HEIGHT as i32 { + /// if let Some(tile) = chunk.get_tile(x, y) { + /// let tile_pos = ( + /// chunk_pos.0 * Chunk::WIDTH as i32 + x, + /// chunk_pos.1 * Chunk::HEIGHT as i32 + y, + /// ); + /// println!("At ({}, {}): {:?}", tile_pos.0, tile_pos.1, tile); + /// } + /// } + /// } + /// } + /// # } else { + /// # panic!("It is wrongly recognised as a finite map"); + /// # } + /// ``` + #[inline] + pub fn chunks(&self) -> impl ExactSizeIterator<Item = ((i32, i32), ChunkWrapper<'map>)> + 'map { + let map: &'map crate::Map = self.map; + self.data + .chunks + .iter() + .map(move |(pos, chunk)| (*pos, ChunkWrapper::new(map, chunk))) + } + + /// Obtains a chunk by its position. To obtain the position of the chunk that contains a tile, + /// use [Chunk::tile_to_chunk_pos]. + #[inline] + pub fn get_chunk(&self, x: i32, y: i32) -> Option<ChunkWrapper<'map>> { + let map: &'map crate::Map = self.map; + self.data + .get_chunk_data(x, y) + .map(move |data| ChunkWrapper::new(map, data)) + } } diff --git a/tests/lib.rs b/tests/lib.rs index 1ab44a8..c0faf29 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -117,7 +117,7 @@ fn test_just_tileset() { } #[test] -fn test_infinite_tileset() { +fn test_infinite_map() { let r = Loader::new() .load_tmx_map("assets/tiled_base64_zlib_infinite.tmx") .unwrap(); -- GitLab