fyrox_impl/scene/tilemap/
data.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use std::{collections::hash_map, path::Path};
22
23use crate::asset::{Resource, ResourceData};
24use fxhash::FxHashMap;
25use fyrox_core::visitor::BinaryBlob;
26
27use crate::core::{
28    algebra::Vector2, reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*,
29};
30
31use super::*;
32
33const CHUNK_WIDTH: usize = 16;
34const CHUNK_HEIGHT: usize = 16;
35const WIDTH_BITS: i32 = (CHUNK_WIDTH - 1) as i32;
36const HEIGHT_BITS: i32 = (CHUNK_HEIGHT - 1) as i32;
37
38/// Resource for storing the tile handles of a tile map.
39pub type TileMapDataResource = Resource<TileMapData>;
40
41/// Given a tile position, calculate the position of the chunk containing that tile
42/// and the position of the tile within that chunk, and return them as a pair:
43/// (chunk position, tile position within chunk)
44fn tile_position_to_chunk_position(position: Vector2<i32>) -> (Vector2<i32>, Vector2<i32>) {
45    let x = position.x;
46    let y = position.y;
47    let x_chunk = x & !WIDTH_BITS;
48    let y_chunk = y & !HEIGHT_BITS;
49    (
50        Vector2::new(x_chunk, y_chunk),
51        Vector2::new(x - x_chunk, y - y_chunk),
52    )
53}
54
55#[derive(Clone, Debug, Reflect)]
56struct Chunk([TileDefinitionHandle; CHUNK_WIDTH * CHUNK_HEIGHT]);
57
58impl Visit for Chunk {
59    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
60        if visitor.is_reading() {
61            let mut data = Vec::default();
62            BinaryBlob { vec: &mut data }.visit(name, visitor)?;
63            if data.len() != CHUNK_WIDTH * CHUNK_HEIGHT {
64                return Err(VisitError::User(
65                    "Wrong number of handles in a chunk".into(),
66                ));
67            }
68            self.0
69                .clone_from_slice(&data[0..CHUNK_WIDTH * CHUNK_HEIGHT]);
70            Ok(())
71        } else {
72            BinaryBlob {
73                vec: &mut self.0.to_vec(),
74            }
75            .visit(name, visitor)
76        }
77    }
78}
79
80impl Default for Chunk {
81    fn default() -> Self {
82        Self([TileDefinitionHandle::EMPTY; CHUNK_WIDTH * CHUNK_HEIGHT])
83    }
84}
85
86impl std::ops::Index<Vector2<i32>> for Chunk {
87    type Output = TileDefinitionHandle;
88
89    fn index(&self, index: Vector2<i32>) -> &Self::Output {
90        let x: usize = index.x.try_into().unwrap();
91        let y: usize = index.y.try_into().unwrap();
92        &self.0[x + y * CHUNK_WIDTH]
93    }
94}
95
96impl std::ops::IndexMut<Vector2<i32>> for Chunk {
97    fn index_mut(&mut self, index: Vector2<i32>) -> &mut Self::Output {
98        let x: usize = index.x.try_into().unwrap();
99        let y: usize = index.y.try_into().unwrap();
100        &mut self.0[x + y * CHUNK_WIDTH]
101    }
102}
103
104impl Chunk {
105    fn iter(&self, offset: Vector2<i32>) -> ChunkIterator {
106        ChunkIterator {
107            position: Vector2::new(0, 0),
108            chunk: self,
109            offset,
110        }
111    }
112    fn is_empty(&self) -> bool {
113        self.0.iter().all(|h| *h == TileDefinitionHandle::EMPTY)
114    }
115}
116
117struct ChunkIterator<'a> {
118    position: Vector2<i32>,
119    offset: Vector2<i32>,
120    chunk: &'a Chunk,
121}
122
123impl Iterator for ChunkIterator<'_> {
124    type Item = (Vector2<i32>, TileDefinitionHandle);
125
126    fn next(&mut self) -> Option<Self::Item> {
127        loop {
128            if self.position.y >= CHUNK_HEIGHT as i32 {
129                return None;
130            }
131            let result_position = self.position;
132            let result = self.chunk[result_position];
133            if self.position.x < (CHUNK_WIDTH - 1) as i32 {
134                self.position.x += 1;
135            } else {
136                self.position.x = 0;
137                self.position.y += 1;
138            }
139            if !result.is_empty() {
140                return Some((result_position + self.offset, result));
141            }
142        }
143    }
144}
145
146/// Iterator over the tiles of a [`TileMapData`] in the form of (position, handle).
147pub struct TileMapDataIterator<'a, P: 'a> {
148    predicate: P,
149    map_iter: hash_map::Iter<'a, Vector2<i32>, Chunk>,
150    chunk_iter: Option<ChunkIterator<'a>>,
151}
152
153impl<P: FnMut(Vector2<i32>) -> bool> Iterator for TileMapDataIterator<'_, P> {
154    type Item = (Vector2<i32>, TileDefinitionHandle);
155    fn next(&mut self) -> Option<Self::Item> {
156        let chunk_iter = match &mut self.chunk_iter {
157            Some(iter) => iter,
158            None => self.next_chunk()?,
159        };
160        if let Some(result) = chunk_iter.next() {
161            Some(result)
162        } else {
163            self.next_chunk()?.next()
164        }
165    }
166}
167
168impl<'a, P: FnMut(Vector2<i32>) -> bool> TileMapDataIterator<'a, P> {
169    fn next_chunk(&mut self) -> Option<&mut ChunkIterator<'a>> {
170        loop {
171            let (pos, chunk) = self.map_iter.next()?;
172            if (self.predicate)(*pos) {
173                return Some(self.chunk_iter.insert(chunk.iter(*pos)));
174            }
175        }
176    }
177}
178
179/// Asset containing the tile handles of a tile map.
180#[derive(Clone, Default, Debug, Reflect, TypeUuidProvider, ComponentProvider)]
181#[type_uuid(id = "a8e4b6b4-c1bd-4ed9-a753-0d5a3dfe1729")]
182pub struct TileMapData {
183    content: FxHashMap<Vector2<i32>, Chunk>,
184}
185
186impl Visit for TileMapData {
187    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
188        if !visitor.is_reading() {
189            self.shrink_to_fit();
190        }
191        self.content.visit(name, visitor)
192    }
193}
194
195impl ResourceData for TileMapData {
196    fn type_uuid(&self) -> Uuid {
197        <Self as TypeUuidProvider>::type_uuid()
198    }
199
200    fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
201        let mut visitor = Visitor::new();
202        self.visit("TileMapData", &mut visitor)?;
203        visitor.save_binary(path)?;
204        Ok(())
205    }
206
207    fn can_be_saved(&self) -> bool {
208        false
209    }
210}
211
212impl TileSource for TileMapData {
213    fn transformation(&self) -> OrthoTransformation {
214        OrthoTransformation::default()
215    }
216
217    fn get_at(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
218        self.get(position)
219    }
220}
221
222impl BoundedTileSource for TileMapData {
223    fn bounding_rect(&self) -> OptionTileRect {
224        let mut rect = OptionTileRect::default();
225        for (pos, _) in self.iter() {
226            rect.push(pos);
227        }
228        rect
229    }
230}
231
232impl TileMapData {
233    /// Iterate over all pairs of (position, handle) in this data.
234    pub fn iter(&self) -> impl Iterator<Item = (Vector2<i32>, TileDefinitionHandle)> + '_ {
235        let map_iter = self.content.iter();
236        TileMapDataIterator {
237            predicate: |_| true,
238            map_iter,
239            chunk_iter: None,
240        }
241    }
242    /// Iterate over all pairs of (position, handle) in this data.
243    pub fn bounded_iter(
244        &self,
245        bounds: OptionTileRect,
246    ) -> impl Iterator<Item = (Vector2<i32>, TileDefinitionHandle)> + '_ {
247        let map_iter = self.content.iter();
248        TileMapDataIterator {
249            predicate: move |pos: Vector2<i32>| {
250                bounds.intersects(TileRect::new(
251                    pos.x,
252                    pos.y,
253                    CHUNK_WIDTH as i32,
254                    CHUNK_HEIGHT as i32,
255                ))
256            },
257            map_iter,
258            chunk_iter: None,
259        }
260    }
261    /// Apply the updates specified in the given `TileUpdate` and modify it so that it
262    /// contains the tiles require to undo the change. Calling `swap_tiles` twice with the same
263    /// `TileUpdate` object will do the changes and then undo them, leaving the tiles unchanged in the end.
264    pub fn swap_tiles(&mut self, tiles: &mut TilesUpdate) {
265        for (p, h) in tiles.iter_mut() {
266            *h = self.replace(*p, *h);
267        }
268    }
269    /// Get the handle for the tile at the given position, if one exists.
270    pub fn get(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
271        let (chunk, pos) = tile_position_to_chunk_position(position);
272        let chunk = self.content.get(&chunk)?;
273        let handle = chunk[pos];
274        if handle.is_empty() {
275            None
276        } else {
277            Some(handle)
278        }
279    }
280    /// Replace the handle at the given position with the given handle and return the original
281    /// handle at that position.
282    pub fn replace(
283        &mut self,
284        position: Vector2<i32>,
285        value: Option<TileDefinitionHandle>,
286    ) -> Option<TileDefinitionHandle> {
287        let (chunk, pos) = tile_position_to_chunk_position(position);
288        if let Some(chunk) = self.content.get_mut(&chunk) {
289            let handle = &mut chunk[pos];
290            let result = *handle;
291            *handle = value.unwrap_or(TileDefinitionHandle::EMPTY);
292            if result.is_empty() {
293                None
294            } else {
295                Some(result)
296            }
297        } else if let Some(value) = value {
298            let chunk = self.content.entry(chunk).or_default();
299            chunk[pos] = value;
300            None
301        } else {
302            None
303        }
304    }
305    /// Set a new handle for the tile at the given position.
306    pub fn set(&mut self, position: Vector2<i32>, value: TileDefinitionHandle) {
307        let (chunk, pos) = tile_position_to_chunk_position(position);
308        let chunk = self.content.entry(chunk).or_default();
309        chunk[pos] = value;
310    }
311    /// Remove the tile at the given position.
312    pub fn remove(&mut self, position: Vector2<i32>) {
313        let (chunk, pos) = tile_position_to_chunk_position(position);
314        if let Some(chunk) = self.content.get_mut(&chunk) {
315            chunk[pos] = TileDefinitionHandle::EMPTY;
316        }
317    }
318    /// Remove all empty chunks.
319    pub fn shrink_to_fit(&mut self) {
320        self.content.retain(|_, v| !v.is_empty())
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use std::cmp::Ordering;
327
328    use super::*;
329
330    fn v(x: i32, y: i32) -> Vector2<i32> {
331        Vector2::new(x, y)
332    }
333
334    fn h(a: i16, b: i16, c: i16, d: i16) -> TileDefinitionHandle {
335        TileDefinitionHandle::new(a, b, c, d)
336    }
337
338    fn v_ord(a: &Vector2<i32>, b: &Vector2<i32>) -> Ordering {
339        a.y.cmp(&b.y).reverse().then(a.x.cmp(&b.x))
340    }
341
342    #[test]
343    fn position_to_chunk() {
344        assert_eq!(
345            tile_position_to_chunk_position(v(16, 16)),
346            (v(16, 16), v(0, 0))
347        );
348        assert_eq!(tile_position_to_chunk_position(v(0, 0)), (v(0, 0), v(0, 0)));
349        assert_eq!(
350            tile_position_to_chunk_position(v(-5, 5)),
351            (v(-16, 0), v(11, 5))
352        );
353        assert_eq!(
354            tile_position_to_chunk_position(v(-16, 5)),
355            (v(-16, 0), v(0, 5))
356        );
357        assert_eq!(
358            tile_position_to_chunk_position(v(-17, 5)),
359            (v(-32, 0), v(15, 5))
360        );
361    }
362    #[test]
363    fn create_chunks() {
364        let mut data = TileMapData::default();
365        let coords = vec![
366            (v(0, 0), h(1, 2, 3, 4)),
367            (v(-1, -2), h(1, 2, 3, 0)),
368            (v(16, 16), h(1, 2, 3, 5)),
369            (v(-1, -1), h(1, 2, 3, 6)),
370            (v(-17, 0), h(1, 2, 3, 7)),
371        ];
372        for (pos, handle) in coords.iter() {
373            data.set(*pos, *handle);
374        }
375        let mut coords = coords
376            .into_iter()
377            .map(|(p, _)| tile_position_to_chunk_position(p).0)
378            .collect::<Vec<_>>();
379        coords.sort_by(v_ord);
380        coords.dedup();
381        let mut result = data.content.keys().copied().collect::<Vec<_>>();
382        result.sort_by(v_ord);
383        assert_eq!(result, coords);
384    }
385    #[test]
386    fn iter_full_chunk() {
387        let mut data = TileMapData::default();
388        let mut required = FxHashSet::default();
389        let mut extra = Vec::default();
390        for x in 0..CHUNK_WIDTH as i32 {
391            for y in 0..CHUNK_HEIGHT as i32 {
392                data.set(v(x, y), h(0, 0, 0, 0));
393                required.insert(v(x, y));
394            }
395        }
396        for (result, _) in data.iter() {
397            if !required.remove(&result) {
398                extra.push(result);
399            }
400        }
401        let required = required.into_iter().collect::<Vec<_>>();
402        assert_eq!((required, extra), (vec![], vec![]));
403    }
404    #[test]
405    fn iter() {
406        let mut data = TileMapData::default();
407        let mut coords = vec![
408            (v(0, 0), h(1, 2, 3, 4)),
409            (v(-1, -2), h(1, 2, 3, 0)),
410            (v(16, 16), h(1, 2, 3, 5)),
411            (v(-1, -1), h(1, 2, 3, 6)),
412            (v(-17, 0), h(1, 2, 3, 7)),
413        ];
414        for (pos, handle) in coords.iter() {
415            data.set(*pos, *handle);
416        }
417        let mut result = data.iter().collect::<Vec<_>>();
418        result.sort_by(|(a, _), (b, _)| v_ord(a, b));
419        coords.sort_by(|(a, _), (b, _)| v_ord(a, b));
420        assert_eq!(result, coords);
421    }
422}