Skip to main content

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