fyrox_impl/scene/tilemap/
tile_source.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
21//! The `tile_source` module contains structs that represent arrangements of tiles
22//! to be used by tile map drawing tools such as rectangular fills and flood fills.
23//! Tile sources can be randomized and they can repeat to create varied effects
24//! while editing tile maps.
25
26use fyrox_core::swap_hash_map_entry;
27
28use crate::{
29    core::{algebra::Vector2, reflect::prelude::*, visitor::prelude::*},
30    fxhash::FxHashMap,
31    rand::{seq::IteratorRandom, thread_rng},
32};
33use std::{
34    cmp::Ordering,
35    fmt::{Debug, Display, Formatter},
36    ops::{Deref, DerefMut},
37    str::FromStr,
38};
39
40use super::*;
41
42/// The type of coordinates stored in a a [TileDefinitionHandle].
43pub type PalettePosition = Vector2<i16>;
44
45#[inline]
46fn try_position(source: Vector2<i32>) -> Option<PalettePosition> {
47    Some(PalettePosition::new(
48        source.x.try_into().ok()?,
49        source.y.try_into().ok()?,
50    ))
51}
52
53#[inline]
54fn position_to_vector(source: PalettePosition) -> Vector2<i32> {
55    source.map(|x| x as i32)
56}
57
58/// A 2D grid that contains tile data.
59#[derive(Default, Debug, Clone, PartialEq, Reflect)]
60pub struct TileGridMap<V: Debug>(FxHashMap<Vector2<i32>, V>);
61
62impl<V: Visit + Default + Debug> Visit for TileGridMap<V> {
63    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
64        self.0.visit(name, visitor)
65    }
66}
67
68impl<V: Debug> Deref for TileGridMap<V> {
69    type Target = FxHashMap<Vector2<i32>, V>;
70    fn deref(&self) -> &Self::Target {
71        &self.0
72    }
73}
74
75impl<V: Debug> DerefMut for TileGridMap<V> {
76    fn deref_mut(&mut self) -> &mut Self::Target {
77        &mut self.0
78    }
79}
80
81/// Position of a tile definition within some tile set
82#[derive(Eq, PartialEq, Clone, Copy, Default, Hash, Reflect, Visit, TypeUuidProvider)]
83#[type_uuid(id = "3eb69303-d361-482d-8094-44b9f9c323ca")]
84#[repr(C)]
85pub struct TileDefinitionHandle {
86    /// Position of the tile's page
87    pub page: PalettePosition,
88    /// Position of the tile definition within the page
89    pub tile: PalettePosition,
90}
91
92unsafe impl bytemuck::Zeroable for TileDefinitionHandle {}
93
94unsafe impl bytemuck::Pod for TileDefinitionHandle {}
95
96impl PartialOrd for TileDefinitionHandle {
97    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
98        Some(self.cmp(other))
99    }
100}
101
102impl Ord for TileDefinitionHandle {
103    fn cmp(&self, other: &Self) -> Ordering {
104        self.page
105            .y
106            .cmp(&other.page.y)
107            .reverse()
108            .then(self.page.x.cmp(&other.page.x))
109            .then(self.tile.y.cmp(&other.tile.y).reverse())
110            .then(self.tile.x.cmp(&other.tile.x))
111    }
112}
113
114impl Display for TileDefinitionHandle {
115    #[inline]
116    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
117        write!(
118            f,
119            "({},{}):({},{})",
120            self.page.x, self.page.y, self.tile.x, self.tile.y
121        )
122    }
123}
124
125impl Debug for TileDefinitionHandle {
126    #[inline]
127    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
128        write!(
129            f,
130            "TileDefinitionHandle({},{};{},{})",
131            self.page.x, self.page.y, self.tile.x, self.tile.y
132        )
133    }
134}
135
136impl FromStr for TileDefinitionHandle {
137    type Err = TileDefinitionHandleParseError;
138
139    fn from_str(s: &str) -> Result<Self, Self::Err> {
140        Self::parse(s).ok_or(TileDefinitionHandleParseError)
141    }
142}
143
144/// An syntax error in parsing a TileDefinitionHandle from a string.
145#[derive(Debug)]
146pub struct TileDefinitionHandleParseError;
147
148impl Display for TileDefinitionHandleParseError {
149    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
150        write!(f, "Tile definition handle parse failure")
151    }
152}
153
154impl Error for TileDefinitionHandleParseError {}
155
156impl TileDefinitionHandle {
157    /// Handle the represents the absence of a tile.
158    pub const EMPTY: Self = Self::new(i16::MIN, i16::MIN, i16::MIN, i16::MIN);
159    /// True if this handle represents there being no tile, [`EMPTY`](Self::EMPTY).
160    pub fn is_empty(&self) -> bool {
161        self == &Self::EMPTY
162    }
163    /// Attempt to construct a handle for the given page and tile positions.
164    /// Handles use a pair of i16 vectors, so that the total is 64 bits.
165    /// If the given vectors are outside of the range that can be represented as i16 coordinates,
166    /// then None is returned.
167    pub fn try_new(page: Vector2<i32>, tile: Vector2<i32>) -> Option<Self> {
168        Some(Self {
169            page: try_position(page)?,
170            tile: try_position(tile)?,
171        })
172    }
173    /// Construct a handle directly from coordinates. This is intended for cases
174    /// where certain tile handles may need to be hard-coded as having special significance.
175    pub const fn new(page_x: i16, page_y: i16, tile_x: i16, tile_y: i16) -> Self {
176        Self {
177            page: PalettePosition::new(page_x, page_y),
178            tile: PalettePosition::new(tile_x, tile_y),
179        }
180    }
181    /// Extracts the page coordinates and converts them to an i32 vector.
182    pub fn page(&self) -> Vector2<i32> {
183        position_to_vector(self.page)
184    }
185    /// Extracts the tile coordinates and converts them to an i32 vector.
186    pub fn tile(&self) -> Vector2<i32> {
187        position_to_vector(self.tile)
188    }
189    /// Convert a string into a tile definition handle by finding four numbers.
190    /// The first two numbers are the page coodrinates. The second two numbers are the tile coordinates.
191    /// None is returned if there are more than four numbers, fewer than four numbers, or any number produces an error in parsing.
192    pub fn parse(s: &str) -> Option<Self> {
193        let mut iter = s
194            .split(|c: char| c != '-' && !c.is_ascii_digit())
195            .filter(|w| !w.is_empty());
196        let a: i16 = iter.next()?.parse().ok()?;
197        let b: i16 = iter.next()?.parse().ok()?;
198        let c: i16 = iter.next()?.parse().ok()?;
199        let d: i16 = iter.next()?.parse().ok()?;
200        if iter.next().is_some() {
201            None
202        } else {
203            Some(Self::new(a, b, c, d))
204        }
205    }
206}
207
208/// A region of tiles to be filled from some source of tiles.
209#[derive(Debug, Default, Clone)]
210pub struct TileRegion {
211    /// The position to put the (0,0) tile of the tile source.
212    /// If `origin` is not within `bounds` then the (0,0) tile will not actually be used.
213    pub origin: Vector2<i32>,
214    /// The area to fill.
215    pub bounds: OptionTileRect,
216}
217
218impl TileRegion {
219    /// Construct a region with its origin in one of the four corners of the given bounds.
220    /// The corner of the origin is based on the given direction.
221    pub fn from_bounds_and_direction(bounds: OptionTileRect, direction: Vector2<i32>) -> Self {
222        let Some(bounds) = *bounds else {
223            return Self::default();
224        };
225        let x0 = if direction.x <= 0 {
226            bounds.left_bottom_corner().x
227        } else {
228            bounds.right_top_corner().x
229        };
230        let y0 = if direction.y <= 0 {
231            bounds.left_bottom_corner().y
232        } else {
233            bounds.right_top_corner().y
234        };
235        Self {
236            origin: Vector2::new(x0, y0),
237            bounds: bounds.into(),
238        }
239    }
240    /// Construct a region with `bounds` that contain `origin` and `end`.
241    pub fn from_points(origin: Vector2<i32>, end: Vector2<i32>) -> Self {
242        Self {
243            origin,
244            bounds: OptionTileRect::from_points(origin, end),
245        }
246    }
247    /// Copy the region and replace its bound.
248    pub fn with_bounds(mut self, bounds: OptionTileRect) -> Self {
249        self.bounds = bounds;
250        self
251    }
252    /// Reduce the size of `bounds` by deflating them by the given amounts.
253    pub fn deflate(mut self, dw: i32, dh: i32) -> Self {
254        self.bounds = self.bounds.deflate(dw, dh);
255        self
256    }
257    /// Iterator over `(target, source)` pairs where `target` is the position to put the tile
258    /// and `source` is the position to get the tile from within the tile source.
259    /// Every position within `bounds` will appear once as the `target`.
260    /// If `origin` is within `bounds`, then `(origin, (0,0))` will be produced by the iterator.
261    pub fn iter(&self) -> impl Iterator<Item = (Vector2<i32>, Vector2<i32>)> + '_ {
262        self.bounds.iter().map(|p| (p, p - self.origin))
263    }
264}
265
266/// A trait for types that can produce a TileDefinitionHandle upon demand,
267/// for use with drawing on tilemaps.
268pub trait TileSource {
269    /// The transformation that should be applied to the tiles before they are written.
270    fn transformation(&self) -> OrthoTransformation;
271    /// Produce a tile definition handle for the given position. If an area of multiple
272    /// tiles is being filled, then the given position represents where the tile
273    /// will go within the area.
274    fn get_at(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle>;
275}
276
277/// A trait for types that can produce a TileDefinitionHandle upon demand,
278/// for use with drawing on tilemaps.
279pub trait BoundedTileSource: TileSource {
280    /// Calculates bounding rectangle in grid coordinates.
281    fn bounding_rect(&self) -> OptionTileRect;
282}
283
284/// A tile source that always produces the same tile.
285#[derive(Clone, Debug)]
286pub struct SingleTileSource(pub OrthoTransformation, pub TileDefinitionHandle);
287
288impl TileSource for SingleTileSource {
289    fn transformation(&self) -> OrthoTransformation {
290        self.0
291    }
292    fn get_at(&self, _position: Vector2<i32>) -> Option<TileDefinitionHandle> {
293        Some(self.1)
294    }
295}
296
297/// A tile source that produces a random tile from the included set of tiles.
298pub struct RandomTileSource<'a>(pub &'a Stamp);
299
300impl TileSource for RandomTileSource<'_> {
301    fn transformation(&self) -> OrthoTransformation {
302        self.0.transformation()
303    }
304    fn get_at(&self, _position: Vector2<i32>) -> Option<TileDefinitionHandle> {
305        self.0.values().choose(&mut thread_rng()).copied()
306    }
307}
308
309/// A tile source that produces a random tile from the included set of tiles.
310pub struct PartialRandomTileSource<'a>(pub &'a Stamp, pub OptionTileRect);
311
312impl TileSource for PartialRandomTileSource<'_> {
313    fn transformation(&self) -> OrthoTransformation {
314        self.0.transformation()
315    }
316    fn get_at(&self, _position: Vector2<i32>) -> Option<TileDefinitionHandle> {
317        let pos = self.1.iter().choose(&mut thread_rng())?;
318        self.0.get_at(pos)
319    }
320}
321
322/// A tile source that adapts another source so that it infinitely repeats the tiles
323/// within the given rect.
324pub struct RepeatTileSource<'a, S> {
325    /// The tiles to repeat
326    pub source: &'a S,
327    /// The region within the stamp to repeat
328    pub region: TileRegion,
329}
330
331impl<S: TileSource> TileSource for RepeatTileSource<'_, S> {
332    fn transformation(&self) -> OrthoTransformation {
333        self.source.transformation()
334    }
335    fn get_at(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
336        let rect = (*self.region.bounds)?;
337        let rect_pos = rect.position;
338        let size = rect.size;
339        let pos = position + self.region.origin - rect_pos;
340        let x = pos.x.rem_euclid(size.x);
341        let y = pos.y.rem_euclid(size.y);
342        self.source.get_at(Vector2::new(x, y) + rect_pos)
343    }
344}
345
346/// A set of tiles.
347#[derive(Clone, Debug, Default, PartialEq)]
348pub struct Tiles(TileGridMap<TileDefinitionHandle>);
349
350/// A set of tiles and a transformation, which represents the tiles that the user has selected
351/// to draw with.
352#[derive(Clone, Debug, Default, Visit)]
353pub struct Stamp(OrthoTransformation, OrthoTransformMap<TileDefinitionHandle>);
354
355impl TileSource for Tiles {
356    fn transformation(&self) -> OrthoTransformation {
357        OrthoTransformation::default()
358    }
359    fn get_at(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
360        self.get(&position).copied()
361    }
362}
363
364impl Visit for Tiles {
365    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
366        self.0.visit(name, visitor)
367    }
368}
369
370impl Deref for Tiles {
371    type Target = TileGridMap<TileDefinitionHandle>;
372
373    fn deref(&self) -> &Self::Target {
374        &self.0
375    }
376}
377
378impl DerefMut for Tiles {
379    fn deref_mut(&mut self) -> &mut Self::Target {
380        &mut self.0
381    }
382}
383
384impl TileSource for Stamp {
385    fn transformation(&self) -> OrthoTransformation {
386        self.0
387    }
388    fn get_at(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
389        self.1.get(position).copied()
390    }
391}
392
393impl Stamp {
394    /// Iterate over the tile handles of the stamp.
395    pub fn tile_iter(&self) -> impl Iterator<Item = TileDefinitionHandle> + '_ {
396        self.1.values().copied()
397    }
398    /// Create a repeating tile source from this stamp to repeat from `start` to `end.`
399    pub fn repeat(&self, start: Vector2<i32>, end: Vector2<i32>) -> RepeatTileSource<Stamp> {
400        let bounds = self.bounding_rect();
401        RepeatTileSource {
402            source: self,
403            region: TileRegion::from_bounds_and_direction(bounds, start - end),
404        }
405    }
406
407    /// Create a repeating tile source from the stamp with no specified direction for the repeat.
408    pub fn repeat_anywhere(&self) -> RepeatTileSource<Stamp> {
409        let bounds = self.bounding_rect();
410        RepeatTileSource {
411            source: self,
412            region: TileRegion::from_bounds_and_direction(bounds, Vector2::new(0, 0)),
413        }
414    }
415
416    /// True if this stamp contains no tiles.
417    pub fn is_empty(&self) -> bool {
418        self.1.is_empty()
419    }
420    /// Turn this stamp into an empty stamp.
421    pub fn clear(&mut self) {
422        self.1.clear();
423        self.0 = OrthoTransformation::identity();
424    }
425    /// Clear this stamp and fill it with the given tiles.
426    /// The tiles are moved so that their center is (0,0).
427    /// The transform is set to identity.
428    pub fn build<I: Iterator<Item = (Vector2<i32>, TileDefinitionHandle)> + Clone>(
429        &mut self,
430        source: I,
431    ) {
432        self.clear();
433        let mut rect = OptionTileRect::default();
434        for (p, _) in source.clone() {
435            rect.push(p);
436        }
437        let Some(rect) = *rect else {
438            return;
439        };
440        let center = rect.center();
441        for (p, h) in source {
442            self.insert(p - center, h);
443        }
444    }
445    /// Rotate the stamp by the given number of 90-degree turns.
446    pub fn rotate(&mut self, amount: i8) {
447        self.0 = self.0.rotated(amount);
448        self.1 = std::mem::take(&mut self.1).rotated(amount);
449    }
450    /// Flip along the x axis.
451    pub fn x_flip(&mut self) {
452        self.0 = self.0.x_flipped();
453        self.1 = std::mem::take(&mut self.1).x_flipped();
454    }
455    /// Flip along the y axis.
456    pub fn y_flip(&mut self) {
457        self.0 = self.0.y_flipped();
458        self.1 = std::mem::take(&mut self.1).y_flipped();
459    }
460    /// Rotate the stamp by the given number of 90-degree turns.
461    pub fn transform(&mut self, amount: OrthoTransformation) {
462        self.0 = self.0.transformed(amount);
463        self.1 = std::mem::take(&mut self.1).transformed(amount);
464    }
465}
466
467impl Deref for Stamp {
468    type Target = OrthoTransformMap<TileDefinitionHandle>;
469    fn deref(&self) -> &Self::Target {
470        &self.1
471    }
472}
473
474impl DerefMut for Stamp {
475    fn deref_mut(&mut self) -> &mut Self::Target {
476        &mut self.1
477    }
478}
479
480impl Tiles {
481    /// Construct a new tile set from the given hash map.
482    pub fn new(source: TileGridMap<TileDefinitionHandle>) -> Self {
483        Self(source)
484    }
485    /// Find the first empty cell in the negative-x direction and the first empty
486    /// cell in the positive-x direction.
487    pub fn find_continuous_horizontal_span(&self, position: Vector2<i32>) -> (i32, i32) {
488        let y = position.y;
489        let mut min = position.x;
490        while self.contains_key(&Vector2::new(min, y)) {
491            min -= 1;
492        }
493        let mut max = position.x;
494        while self.contains_key(&Vector2::new(max, y)) {
495            max += 1;
496        }
497        (min, max)
498    }
499    /// Apply the updates specified in the given `TileUpdate` and modify it so that it
500    /// contains the tiles require to undo the change. Calling `swap_tiles` twice with the same
501    /// `TileUpdate` object will do the changes and then undo them, leaving the tiles unchanged in the end.
502    pub fn swap_tiles(&mut self, updates: &mut TilesUpdate) {
503        for (k, v) in updates.iter_mut() {
504            swap_hash_map_entry(self.entry(*k), v);
505        }
506    }
507    /// Calculates bounding rectangle in grid coordinates.
508    #[inline]
509    pub fn bounding_rect(&self) -> OptionTileRect {
510        let mut result = OptionTileRect::default();
511        for position in self.keys() {
512            result.push(*position);
513        }
514        result
515    }
516
517    /// Clears the tile container.
518    #[inline]
519    pub fn clear(&mut self) {
520        self.0.clear();
521    }
522}
523
524#[cfg(test)]
525mod tests {
526    use super::*;
527
528    /// Checking that TileDefinitionHandle is using the expected data layout as
529    /// required for its unsafe `bytemuck::Pod` implementation.
530    #[test]
531    fn size_of_handle() {
532        assert_eq!(std::mem::size_of::<TileDefinitionHandle>(), 8);
533    }
534
535    #[test]
536    fn zero_handle() {
537        assert_eq!(
538            TileDefinitionHandle::zeroed(),
539            TileDefinitionHandle::default()
540        );
541    }
542}