Skip to main content

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_ascii_to_file(path)?;
204        Ok(())
205    }
206
207    fn can_be_saved(&self) -> bool {
208        false
209    }
210
211    fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
212        Some(Box::new(self.clone()))
213    }
214}
215
216impl TileSource for TileMapData {
217    fn brush(&self) -> Option<&TileMapBrushResource> {
218        None
219    }
220    fn transformation(&self) -> OrthoTransformation {
221        OrthoTransformation::default()
222    }
223
224    fn get_at(&self, position: Vector2<i32>) -> Option<StampElement> {
225        self.get(position).map(|h| h.into())
226    }
227}
228
229impl BoundedTileSource for TileMapData {
230    fn bounding_rect(&self) -> OptionTileRect {
231        let mut rect = OptionTileRect::default();
232        for (pos, _) in self.iter() {
233            rect.push(pos);
234        }
235        rect
236    }
237}
238
239impl TileMapData {
240    /// Iterate over all pairs of (position, handle) in this data.
241    pub fn iter(&self) -> impl Iterator<Item = (Vector2<i32>, TileDefinitionHandle)> + '_ {
242        let map_iter = self.content.iter();
243        TileMapDataIterator {
244            predicate: |_| true,
245            map_iter,
246            chunk_iter: None,
247        }
248    }
249    /// Iterate over all pairs of (position, handle) in this data.
250    pub fn bounded_iter(
251        &self,
252        bounds: OptionTileRect,
253    ) -> impl Iterator<Item = (Vector2<i32>, TileDefinitionHandle)> + '_ {
254        let map_iter = self.content.iter();
255        TileMapDataIterator {
256            predicate: move |pos: Vector2<i32>| {
257                bounds.intersects(TileRect::new(
258                    pos.x,
259                    pos.y,
260                    CHUNK_WIDTH as i32,
261                    CHUNK_HEIGHT as i32,
262                ))
263            },
264            map_iter,
265            chunk_iter: None,
266        }
267    }
268    /// Apply the updates specified in the given `TileUpdate` and modify it so that it
269    /// contains the tiles require to undo the change. Calling `swap_tiles` twice with the same
270    /// `TileUpdate` object will do the changes and then undo them, leaving the tiles unchanged in the end.
271    pub fn swap_tiles(&mut self, tiles: &mut TilesUpdate) {
272        for (p, h) in tiles.iter_mut() {
273            *h = self.replace(*p, *h);
274        }
275    }
276    /// Get the handle for the tile at the given position, if one exists.
277    pub fn get(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
278        let (chunk, pos) = tile_position_to_chunk_position(position);
279        let chunk = self.content.get(&chunk)?;
280        let handle = chunk[pos];
281        if handle.is_empty() {
282            None
283        } else {
284            Some(handle)
285        }
286    }
287    /// Replace the handle at the given position with the given handle and return the original
288    /// handle at that position.
289    pub fn replace(
290        &mut self,
291        position: Vector2<i32>,
292        value: Option<TileDefinitionHandle>,
293    ) -> Option<TileDefinitionHandle> {
294        let (chunk, pos) = tile_position_to_chunk_position(position);
295        if let Some(chunk) = self.content.get_mut(&chunk) {
296            let handle = &mut chunk[pos];
297            let result = *handle;
298            *handle = value.unwrap_or(TileDefinitionHandle::EMPTY);
299            if result.is_empty() {
300                None
301            } else {
302                Some(result)
303            }
304        } else if let Some(value) = value {
305            let chunk = self.content.entry(chunk).or_default();
306            chunk[pos] = value;
307            None
308        } else {
309            None
310        }
311    }
312    /// Set a new handle for the tile at the given position.
313    pub fn set(&mut self, position: Vector2<i32>, value: TileDefinitionHandle) {
314        let (chunk, pos) = tile_position_to_chunk_position(position);
315        let chunk = self.content.entry(chunk).or_default();
316        chunk[pos] = value;
317    }
318    /// Remove the tile at the given position.
319    pub fn remove(&mut self, position: Vector2<i32>) {
320        let (chunk, pos) = tile_position_to_chunk_position(position);
321        if let Some(chunk) = self.content.get_mut(&chunk) {
322            chunk[pos] = TileDefinitionHandle::EMPTY;
323        }
324    }
325    /// Remove all empty chunks.
326    pub fn shrink_to_fit(&mut self) {
327        self.content.retain(|_, v| !v.is_empty())
328    }
329}
330
331#[cfg(test)]
332mod tests {
333    use std::cmp::Ordering;
334
335    use super::*;
336
337    fn v(x: i32, y: i32) -> Vector2<i32> {
338        Vector2::new(x, y)
339    }
340
341    fn h(a: i16, b: i16, c: i16, d: i16) -> TileDefinitionHandle {
342        TileDefinitionHandle::new(a, b, c, d)
343    }
344
345    fn v_ord(a: &Vector2<i32>, b: &Vector2<i32>) -> Ordering {
346        a.y.cmp(&b.y).reverse().then(a.x.cmp(&b.x))
347    }
348
349    #[test]
350    fn position_to_chunk() {
351        assert_eq!(
352            tile_position_to_chunk_position(v(16, 16)),
353            (v(16, 16), v(0, 0))
354        );
355        assert_eq!(tile_position_to_chunk_position(v(0, 0)), (v(0, 0), v(0, 0)));
356        assert_eq!(
357            tile_position_to_chunk_position(v(-5, 5)),
358            (v(-16, 0), v(11, 5))
359        );
360        assert_eq!(
361            tile_position_to_chunk_position(v(-16, 5)),
362            (v(-16, 0), v(0, 5))
363        );
364        assert_eq!(
365            tile_position_to_chunk_position(v(-17, 5)),
366            (v(-32, 0), v(15, 5))
367        );
368    }
369    #[test]
370    fn create_chunks() {
371        let mut data = TileMapData::default();
372        let coords = vec![
373            (v(0, 0), h(1, 2, 3, 4)),
374            (v(-1, -2), h(1, 2, 3, 0)),
375            (v(16, 16), h(1, 2, 3, 5)),
376            (v(-1, -1), h(1, 2, 3, 6)),
377            (v(-17, 0), h(1, 2, 3, 7)),
378        ];
379        for (pos, handle) in coords.iter() {
380            data.set(*pos, *handle);
381        }
382        let mut coords = coords
383            .into_iter()
384            .map(|(p, _)| tile_position_to_chunk_position(p).0)
385            .collect::<Vec<_>>();
386        coords.sort_by(v_ord);
387        coords.dedup();
388        let mut result = data.content.keys().copied().collect::<Vec<_>>();
389        result.sort_by(v_ord);
390        assert_eq!(result, coords);
391    }
392    #[test]
393    fn iter_full_chunk() {
394        let mut data = TileMapData::default();
395        let mut required = FxHashSet::default();
396        let mut extra = Vec::default();
397        for x in 0..CHUNK_WIDTH as i32 {
398            for y in 0..CHUNK_HEIGHT as i32 {
399                data.set(v(x, y), h(0, 0, 0, 0));
400                required.insert(v(x, y));
401            }
402        }
403        for (result, _) in data.iter() {
404            if !required.remove(&result) {
405                extra.push(result);
406            }
407        }
408        let required = required.into_iter().collect::<Vec<_>>();
409        assert_eq!((required, extra), (vec![], vec![]));
410    }
411    #[test]
412    fn iter() {
413        let mut data = TileMapData::default();
414        let mut coords = vec![
415            (v(0, 0), h(1, 2, 3, 4)),
416            (v(-1, -2), h(1, 2, 3, 0)),
417            (v(16, 16), h(1, 2, 3, 5)),
418            (v(-1, -1), h(1, 2, 3, 6)),
419            (v(-17, 0), h(1, 2, 3, 7)),
420        ];
421        for (pos, handle) in coords.iter() {
422            data.set(*pos, *handle);
423        }
424        let mut result = data.iter().collect::<Vec<_>>();
425        result.sort_by(|(a, _), (b, _)| v_ord(a, b));
426        coords.sort_by(|(a, _), (b, _)| v_ord(a, b));
427        assert_eq!(result, coords);
428    }
429}