Skip to main content

fyrox_impl/scene/tilemap/
tileset.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//! Tile set is a special storage for tile descriptions. It is a sort of database, that contains
22//! descriptions (definitions) for tiles. Tiles are organized into pages. Each page has particular
23//! (x,y) coordinates and each tile within the page has its own (x,y) coordinates, so finding a tile
24//! requires two pairs of coordinates (x,y):(x,y), which is called a [`TileDefinitionHandle`].
25//!
26//! A [`TileMap`] stores a `TileDefinitionHandle` for each cell, and it uses those handles to index
27//! into a tile set to know how each cell should be rendered.
28//!
29//! See [`TileSet`] docs for more info and usage examples.
30
31use crate::{
32    asset::{
33        io::ResourceIo,
34        loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader},
35        manager::ResourceManager,
36        state::LoadError,
37        Resource, ResourceData, ResourceDataRef,
38    },
39    core::{
40        algebra::Vector2, color::Color, io::FileError, log::Log, reflect::prelude::*,
41        type_traits::prelude::*, visitor::prelude::*, ImmutableString,
42    },
43    fxhash::{FxHashMap, FxHashSet},
44    material::{MaterialResource, MaterialResourceExtension},
45    resource::texture::TextureResource,
46};
47use std::{
48    collections::hash_map::{self, Entry, Keys},
49    error::Error,
50    fmt::{Display, Formatter},
51    ops::{Deref, DerefMut},
52    path::{Path, PathBuf},
53    sync::Arc,
54};
55
56use super::*;
57use fyrox_core::{swap_hash_map_entries, swap_hash_map_entry};
58pub use property::*;
59
60const DEFAULT_TILE_SIZE: Vector2<u32> = Vector2::new(16, 16);
61const DEFAULT_ANIMATION_FRAME_RATE: f32 = 12.0;
62
63/// The color that is used to represent a tile where the property value matches the value that is
64/// currently be drawn. This is only used when the the property value does not have a specially assigned color.
65pub const ELEMENT_MATCH_HIGHLIGHT_COLOR: Color = Color::from_rgba(255, 255, 0, 200);
66
67/// An error that may occur during tile set resource loading.
68#[derive(Debug)]
69pub enum TileSetResourceError {
70    /// An i/o error has occurred.
71    Io(FileError),
72
73    /// An error that may occur due to version incompatibilities.
74    Visit(VisitError),
75}
76
77impl Display for TileSetResourceError {
78    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
79        match self {
80            Self::Io(v) => {
81                write!(f, "A file load error has occurred {v:?}")
82            }
83            Self::Visit(v) => {
84                write!(
85                    f,
86                    "An error that may occur due to version incompatibilities. {v:?}"
87                )
88            }
89        }
90    }
91}
92
93impl From<FileError> for TileSetResourceError {
94    fn from(e: FileError) -> Self {
95        Self::Io(e)
96    }
97}
98
99impl From<VisitError> for TileSetResourceError {
100    fn from(e: VisitError) -> Self {
101        Self::Visit(e)
102    }
103}
104
105/// Definition of a tile.
106#[derive(Clone, Default, PartialEq, Debug, Reflect, Visit)]
107#[visit(optional)]
108pub struct TileDefinition {
109    /// The tile's material resource and the rect within that material that should be rendered as the tile.
110    pub material_bounds: TileMaterialBounds,
111    /// Miscellaneous data that is stored with the tile definition.
112    pub data: TileData,
113}
114
115impl TileDefinition {
116    fn from_material_bounds(material_bounds: TileMaterialBounds) -> Self {
117        Self {
118            material_bounds,
119            data: TileData::default(),
120        }
121    }
122}
123
124impl OrthoTransform for TileDefinition {
125    fn x_flipped(mut self) -> Self {
126        self.material_bounds = self.material_bounds.x_flipped();
127        self.data = self.data.x_flipped();
128        self
129    }
130
131    fn rotated(mut self, amount: i8) -> Self {
132        self.material_bounds = self.material_bounds.rotated(amount);
133        self.data = self.data.rotated(amount);
134        self
135    }
136}
137
138/// A tile's material resource and the rect within that material that should be rendered as the tile.
139#[derive(Clone, PartialEq, Debug, Reflect, Visit)]
140pub struct TileMaterialBounds {
141    /// Material of the tile.
142    pub material: MaterialResource,
143    /// A rectangle that defines pixel coordinates range for the tile within the material
144    pub bounds: TileBounds,
145}
146
147impl Default for TileMaterialBounds {
148    fn default() -> Self {
149        Self {
150            material: Resource::new_ok(
151                Uuid::new_v4(),
152                ResourceKind::Embedded,
153                Material::standard_tile(),
154            ),
155            bounds: Default::default(),
156        }
157    }
158}
159
160impl OrthoTransform for TileMaterialBounds {
161    fn x_flipped(mut self) -> Self {
162        self.bounds = self.bounds.x_flipped();
163        self
164    }
165
166    fn rotated(mut self, amount: i8) -> Self {
167        self.bounds = self.bounds.rotated(amount);
168        self
169    }
170}
171
172/// Miscellaneous data that is stored with the tile definition.
173#[derive(Clone, Default, PartialEq, Debug, Reflect, Visit)]
174#[visit(optional)]
175pub struct TileData {
176    /// Colliders of the tile.
177    pub colliders: FxHashMap<Uuid, TileCollider>,
178    /// Color of the tile.
179    pub color: Color,
180    /// A custom set of properties. Properties could be used to assign additional information for
181    /// tiles, such surface type (for example, lava, ice, dirt, etc.), physics properties and so
182    /// on.
183    pub properties: FxHashMap<Uuid, TileSetPropertyValue>,
184}
185
186impl OrthoTransform for TileData {
187    fn x_flipped(mut self) -> Self {
188        for (_, value) in self.properties.iter_mut() {
189            *value = value.clone().x_flipped();
190        }
191        for (_, value) in self.colliders.iter_mut() {
192            *value = value.clone().x_flipped();
193        }
194        self
195    }
196
197    fn rotated(mut self, amount: i8) -> Self {
198        for (_, value) in self.properties.iter_mut() {
199            *value = value.clone().rotated(amount);
200        }
201        for (_, value) in self.colliders.iter_mut() {
202            *value = value.clone().rotated(amount);
203        }
204        self
205    }
206}
207
208/// Specify the pixel coordinates of each corner of a tile within some material.
209/// This is used in place of a standard `Rect` because tiles can be flipped and rotated
210/// and otherwise transformed with respect the their source material, so for example the
211/// left-top corner of the tile may be to the right of the right-top corner in the material.
212#[derive(Clone, Default, PartialEq, Debug, Reflect, Visit)]
213pub struct TileBounds {
214    /// The position of the tile's left-top corner within its source material.
215    pub left_top_corner: Vector2<u32>,
216    /// The position of the tile's right-top corner within its source material.
217    pub right_top_corner: Vector2<u32>,
218    /// The position of the tile's left-bottom corner within its source material.
219    pub right_bottom_corner: Vector2<u32>,
220    /// The position of the tile's right-bottom corner within its source material.
221    pub left_bottom_corner: Vector2<u32>,
222}
223
224fn pixel_coords_to_uv(position: Vector2<u32>, total_size: Vector2<u32>) -> Vector2<f32> {
225    Vector2::new(
226        position.x as f32 / total_size.x as f32,
227        position.y as f32 / total_size.y as f32,
228    )
229}
230
231impl TileBounds {
232    /// Translates the pixel position of the corner of the tile into UV coordinates between 0 and 1.
233    pub fn left_top_uv(&self, texture_size: Vector2<u32>) -> Vector2<f32> {
234        pixel_coords_to_uv(self.left_top_corner, texture_size)
235    }
236    /// Translates the pixel position of the corner of the tile into UV coordinates between 0 and 1.
237    pub fn right_top_uv(&self, texture_size: Vector2<u32>) -> Vector2<f32> {
238        pixel_coords_to_uv(self.right_top_corner, texture_size)
239    }
240    /// Translates the pixel position of the corner of the tile into UV coordinates between 0 and 1.
241    pub fn left_bottom_uv(&self, texture_size: Vector2<u32>) -> Vector2<f32> {
242        pixel_coords_to_uv(self.left_bottom_corner, texture_size)
243    }
244    /// Translates the pixel position of the corner of the tile into UV coordinates between 0 and 1.
245    pub fn right_bottom_uv(&self, texture_size: Vector2<u32>) -> Vector2<f32> {
246        pixel_coords_to_uv(self.right_bottom_corner, texture_size)
247    }
248    /// Get a corner position based on an index from 0 to 3, in this order:
249    /// left_bottom, right_bottom, right_top, left_top
250    pub fn get(&self, index: usize) -> Vector2<u32> {
251        match index {
252            0 => self.left_bottom_corner,
253            1 => self.right_bottom_corner,
254            2 => self.right_top_corner,
255            3 => self.left_top_corner,
256            _ => panic!(),
257        }
258    }
259    /// Modify a corner position based on an index from 0 to 3.
260    /// left_bottom, right_bottom, right_top, left_top
261    pub fn get_mut(&mut self, index: usize) -> &mut Vector2<u32> {
262        match index {
263            0 => &mut self.left_bottom_corner,
264            1 => &mut self.right_bottom_corner,
265            2 => &mut self.right_top_corner,
266            3 => &mut self.left_top_corner,
267            _ => panic!(),
268        }
269    }
270}
271
272impl OrthoTransform for TileBounds {
273    fn x_flipped(mut self) -> Self {
274        std::mem::swap(&mut self.left_top_corner, &mut self.right_top_corner);
275        std::mem::swap(&mut self.left_bottom_corner, &mut self.right_bottom_corner);
276        self
277    }
278
279    fn rotated(mut self, amount: i8) -> Self {
280        let old = self.clone();
281        let amount = amount.rem_euclid(4) as usize;
282        for i in 0..4 {
283            *self.get_mut((i + amount).rem_euclid(4)) = old.get(i);
284        }
285        self
286    }
287}
288
289/// A tile set can contain multiple pages of tiles, and each page may have its own
290/// independent source of tile data.
291#[derive(Clone, Default, Debug, Visit, Reflect)]
292pub struct TileSetPage {
293    /// The tile that represents this page in the editor
294    pub icon: TileDefinitionHandle,
295    /// The source of the page's data.
296    pub source: TileSetPageSource,
297}
298
299impl TileSetPage {
300    /// True if this page is a material page.
301    pub fn is_material(&self) -> bool {
302        matches!(self.source, TileSetPageSource::Atlas(_))
303    }
304    /// True if this page is a freeform tile page.
305    pub fn is_freeform(&self) -> bool {
306        matches!(self.source, TileSetPageSource::Freeform(_))
307    }
308    /// True if this page contains tile transform groups.
309    pub fn is_transform_set(&self) -> bool {
310        matches!(self.source, TileSetPageSource::Transform(_))
311    }
312    /// True if this page contains tile transform groups.
313    pub fn is_animation(&self) -> bool {
314        matches!(self.source, TileSetPageSource::Animation(_))
315    }
316    /// The type of this page.
317    pub fn page_type(&self) -> PageType {
318        self.source.page_type()
319    }
320    /// True if a tile exists at the given position in this page.
321    pub fn has_tile_at(&self, position: Vector2<i32>) -> bool {
322        match &self.source {
323            TileSetPageSource::Atlas(mat) => mat.tiles.contains_key(&position),
324            TileSetPageSource::Freeform(map) => map.contains_key(&position),
325            TileSetPageSource::Transform(tiles) => tiles.contains_key(&position),
326            TileSetPageSource::Animation(tiles) => tiles.contains_key(&position),
327        }
328    }
329    /// Generate a list of all tile positions in this page.
330    pub fn keys(&self) -> Vec<Vector2<i32>> {
331        match &self.source {
332            TileSetPageSource::Atlas(mat) => mat.tiles.keys().copied().collect(),
333            TileSetPageSource::Freeform(map) => map.keys().copied().collect(),
334            TileSetPageSource::Transform(tiles) => tiles.keys().copied().collect(),
335            TileSetPageSource::Animation(tiles) => tiles.keys().copied().collect(),
336        }
337    }
338    /// The rect that contains all the tiles of this page.
339    pub fn get_bounds(&self) -> OptionTileRect {
340        let mut result = OptionTileRect::default();
341        match &self.source {
342            TileSetPageSource::Atlas(mat) => {
343                for pos in mat.tiles.keys() {
344                    result.push(*pos);
345                }
346            }
347            TileSetPageSource::Freeform(map) => {
348                for pos in map.keys() {
349                    result.push(*pos);
350                }
351            }
352            TileSetPageSource::Transform(tiles) => {
353                for pos in tiles.keys() {
354                    result.push(*pos);
355                }
356            }
357            TileSetPageSource::Animation(tiles) => {
358                for pos in tiles.keys() {
359                    result.push(*pos);
360                }
361            }
362        }
363        result
364    }
365    /// Change this tile set according to the specifications in `update`, and change `update` so
366    /// that it would refers the change to this tile set, if it were applied again at the same position.
367    pub fn swap_tile(&mut self, position: Vector2<i32>, update: &mut TileDataUpdate) {
368        match &mut self.source {
369            TileSetPageSource::Atlas(map0) => swap_material_tile(map0, position, update),
370            TileSetPageSource::Freeform(map0) => swap_freeform_tile(map0, position, update),
371            TileSetPageSource::Transform(map0) => swap_transform_tile(map0, position, update),
372            TileSetPageSource::Animation(map0) => swap_animation_tile(map0, position, update),
373        }
374    }
375
376    /// Take all the values for the property with the given id, remove them from the page, and put them into the given hash map.
377    /// At the same time, take all the values from the given hash map and put them into the page.
378    pub fn swap_all_values_for_property(
379        &mut self,
380        page: Vector2<i32>,
381        property_id: Uuid,
382        values: &mut FxHashMap<TileDefinitionHandle, TileSetPropertyValue>,
383    ) {
384        match &mut self.source {
385            TileSetPageSource::Atlas(map0) => {
386                let tiles = &mut map0.tiles;
387                for (tile, data) in tiles.iter_mut() {
388                    let Some(handle) = TileDefinitionHandle::try_new(page, *tile) else {
389                        continue;
390                    };
391                    swap_hash_map_entries(data.properties.entry(property_id), values.entry(handle));
392                }
393            }
394            TileSetPageSource::Freeform(tiles) => {
395                for (tile, tile_def) in tiles.iter_mut() {
396                    let Some(handle) = TileDefinitionHandle::try_new(page, *tile) else {
397                        continue;
398                    };
399                    swap_hash_map_entries(
400                        tile_def.data.properties.entry(property_id),
401                        values.entry(handle),
402                    );
403                }
404            }
405            TileSetPageSource::Transform(_) => (),
406            TileSetPageSource::Animation(_) => (),
407        }
408    }
409    /// Take all the colliders for the given collider id, remove them from the page, and put them into the given hash map.
410    /// At the same time, take all the colliders from the given hash map and put them into the page.
411    pub fn swap_all_values_for_collider(
412        &mut self,
413        page: Vector2<i32>,
414        collider_id: Uuid,
415        values: &mut FxHashMap<TileDefinitionHandle, TileCollider>,
416    ) {
417        match &mut self.source {
418            TileSetPageSource::Atlas(map0) => {
419                let tiles = &mut map0.tiles;
420                for (tile, data) in tiles.iter_mut() {
421                    let Some(handle) = TileDefinitionHandle::try_new(page, *tile) else {
422                        continue;
423                    };
424                    swap_hash_map_entries(data.colliders.entry(collider_id), values.entry(handle));
425                }
426            }
427            TileSetPageSource::Freeform(tiles) => {
428                for (tile, tile_def) in tiles.iter_mut() {
429                    let Some(handle) = TileDefinitionHandle::try_new(page, *tile) else {
430                        continue;
431                    };
432                    swap_hash_map_entries(
433                        tile_def.data.colliders.entry(collider_id),
434                        values.entry(handle),
435                    );
436                }
437            }
438            _ => panic!(),
439        }
440    }
441}
442
443fn swap_material_tile(
444    map0: &mut TileMaterial,
445    position: Vector2<i32>,
446    update: &mut TileDataUpdate,
447) {
448    let e0 = map0.tiles.entry(position);
449    match (e0, update) {
450        (Entry::Occupied(d0), d1 @ TileDataUpdate::Erase) => {
451            *d1 = TileDataUpdate::MaterialTile(d0.remove())
452        }
453        (Entry::Vacant(d0), d1 @ TileDataUpdate::MaterialTile(_)) => {
454            d0.insert(d1.take_data());
455        }
456        (Entry::Vacant(_), TileDataUpdate::Erase) => (),
457        (Entry::Occupied(mut d0), d1) => d1.swap_with_data(d0.get_mut()),
458        (Entry::Vacant(d0), d1) => {
459            let mut data = TileData::default();
460            d1.swap_with_data(&mut data);
461            let _ = d0.insert(data);
462            *d1 = TileDataUpdate::Erase;
463        }
464    }
465}
466fn swap_freeform_tile(
467    map0: &mut TileGridMap<TileDefinition>,
468    position: Vector2<i32>,
469    update: &mut TileDataUpdate,
470) {
471    let e0 = map0.entry(position);
472    match (e0, update) {
473        (Entry::Occupied(mut d0), TileDataUpdate::Material(d1)) => {
474            std::mem::swap(&mut d0.get_mut().material_bounds, d1)
475        }
476        (Entry::Vacant(d0), d1 @ TileDataUpdate::Material(_)) => {
477            let TileDataUpdate::Material(material_bounds) = std::mem::take(d1) else {
478                unreachable!();
479            };
480            let def = TileDefinition::from_material_bounds(material_bounds);
481            let _ = d0.insert(def);
482        }
483        (Entry::Occupied(mut d0), TileDataUpdate::FreeformTile(d1)) => {
484            std::mem::swap(d0.get_mut(), d1);
485        }
486        (Entry::Occupied(d0), d1 @ TileDataUpdate::Erase) => {
487            *d1 = TileDataUpdate::FreeformTile(d0.remove())
488        }
489        (Entry::Vacant(d0), d1 @ TileDataUpdate::FreeformTile(_)) => {
490            d0.insert(d1.take_definition());
491        }
492        (Entry::Vacant(_), TileDataUpdate::Erase) => (),
493        (Entry::Occupied(mut d0), d1) => d1.swap_with_data(&mut d0.get_mut().data),
494        (Entry::Vacant(_), d1) => {
495            // We cannot create a freeform tile without a material, so destroy the unusable data in d1.
496            *d1 = TileDataUpdate::Erase;
497        }
498    }
499}
500
501fn swap_transform_tile(
502    map0: &mut TransformSetTiles,
503    position: Vector2<i32>,
504    update: &mut TileDataUpdate,
505) {
506    let e0 = map0.entry(position);
507    let TileDataUpdate::TransformSet(handle) = update else {
508        panic!()
509    };
510    swap_hash_map_entry(e0, handle);
511}
512
513fn swap_animation_tile(
514    map0: &mut AnimationTiles,
515    position: Vector2<i32>,
516    update: &mut TileDataUpdate,
517) {
518    let e0 = map0.entry(position);
519    let TileDataUpdate::TransformSet(handle) = update else {
520        panic!()
521    };
522    swap_hash_map_entry(e0, handle);
523}
524
525/// A tile set contains three forms of tile, depending on the type of page.
526/// This enum can represent a tile in any of those three forms.
527#[derive(Debug, Clone)]
528pub enum AbstractTile {
529    /// A material tile contains data but no material information, because
530    /// the material information comes from an atlas that spans the entire page.
531    Atlas(TileData),
532    /// A freeform tile contains a complete definition, including material,
533    /// the UV bounds of the tile within that material, and tile data.
534    /// Freeform tiles are the most flexible kind of tile.
535    Freeform(TileDefinition),
536    /// A transform tile contains no data, but it has a handle that refers to
537    /// some tile somewhere else in the set. A transform page contains
538    /// transform tiles in groups of 8 and specifies how its tiles are to be
539    /// rotated and flipped.
540    Transform(TileDefinitionHandle),
541}
542
543/// This is where tile set pages store their tile data.
544#[derive(Clone, PartialEq, Debug, Visit, Reflect)]
545pub enum TileSetPageSource {
546    /// A page that gets its data from a material resource and arranges its tiles according to their positions in the material.
547    /// All tiles in an atlas page share the same material and their UV data is automatically calculated based on the position
548    /// of the tile on the page, with the tile at (0,-1) being the top-left corner of the material, and negative-y going toward
549    /// the bottom of the material.
550    Atlas(TileMaterial),
551    /// A page that contains arbitrary tile definitions. These tiles are free to specify any material and any UV values for each
552    /// tile, and tiles can be positioned anywhere on the page.
553    Freeform(TileGridMap<TileDefinition>),
554    /// A page that contains no tile definitions, but contains handles likes a brush.
555    /// Handles into a transform set page can be used to connect a tile to a transformed version of that tile.
556    /// Tiles are arranged in groups of 8, one for each of four 90-degree rotations, and four horizontally flipped 90-degree rotations.
557    /// No two transform tiles may share the same handle, because that would
558    /// cause the transformations to be ambiguous.
559    Transform(TransformSetTiles),
560    /// A page that contains no tile data, but contains handles referencing tiles
561    /// on other pages and specifies how tiles animate over time.
562    /// Animations proceed from left-to-right, with increasing x-coordinate,
563    /// along continuous rows of tiles, until an empty cell is found, and then
564    /// the animation returns to the start of the sequence and repeats.
565    Animation(AnimationTiles),
566}
567
568impl Default for TileSetPageSource {
569    fn default() -> Self {
570        Self::Atlas(TileMaterial::default())
571    }
572}
573
574impl TileSetPageSource {
575    /// Create a new default material page.
576    pub fn new_material() -> Self {
577        Self::Atlas(TileMaterial::default())
578    }
579    /// Create a new default freeform page.
580    pub fn new_free() -> Self {
581        Self::Freeform(TileGridMap::default())
582    }
583    /// Create a new default transform page.
584    pub fn new_transform() -> Self {
585        Self::Transform(TransformSetTiles::default())
586    }
587    /// Create a new default transform page.
588    pub fn new_animation() -> Self {
589        Self::Animation(AnimationTiles::default())
590    }
591    /// The type of this page.
592    pub fn page_type(&self) -> PageType {
593        match self {
594            TileSetPageSource::Atlas(_) => PageType::Atlas,
595            TileSetPageSource::Freeform(_) => PageType::Freeform,
596            TileSetPageSource::Transform(_) => PageType::Transform,
597            TileSetPageSource::Animation(_) => PageType::Animation,
598        }
599    }
600    /// True if this page contains some tile at the given position.
601    pub fn contains_tile_at(&self, position: Vector2<i32>) -> bool {
602        match self {
603            TileSetPageSource::Atlas(map) => map.contains_key(&position),
604            TileSetPageSource::Freeform(map) => map.contains_key(&position),
605            TileSetPageSource::Transform(map) => map.contains_key(&position),
606            TileSetPageSource::Animation(map) => map.contains_key(&position),
607        }
608    }
609}
610
611/// The tile data for transform set pages of a [`TileSet`].
612/// Each transform set page contains a hash map of [`TileDefinitionHandle`]
613/// that are divided into groups of 8, and each group is arranged into 2x4,
614/// with two rows and four columns. The left 2x2 represent four 90-degree rotations
615/// of a tile, so any tile within that 2x2 can be transformed into any other by rotation.
616/// The right 2x2 represents the same rotated tile but flipped horizontally, which allows
617/// for any combination of 90-degree rotations, horizontal flips, and vertical flips by
618/// moving around the 2x4 grid.
619#[derive(Default, Clone, PartialEq, Debug, Reflect, Visit)]
620pub struct TransformSetTiles(
621    #[reflect(hidden)]
622    #[visit(optional)]
623    pub Tiles,
624);
625
626impl Deref for TransformSetTiles {
627    type Target = Tiles;
628
629    fn deref(&self) -> &Self::Target {
630        &self.0
631    }
632}
633
634impl DerefMut for TransformSetTiles {
635    fn deref_mut(&mut self) -> &mut Self::Target {
636        &mut self.0
637    }
638}
639
640/// A page that contains no tile data, but contains handles referencing tiles
641/// on other pages and specifies how tiles animate over time.
642/// Animations proceed from left-to-right, with increasing x-coordinate,
643/// along continuous rows of tiles, until an empty cell is found, and then
644/// the animation returns to the start of the sequence and repeats.
645#[derive(Clone, PartialEq, Debug, Reflect, Visit)]
646pub struct AnimationTiles {
647    /// The speed of the animation in frames per second.
648    pub frame_rate: f32,
649    /// The tile animation sequences represented as a grid of [`TileDefinitionHandle`].
650    #[reflect(hidden)]
651    #[visit(optional)]
652    pub tiles: Tiles,
653}
654
655impl Default for AnimationTiles {
656    fn default() -> Self {
657        Self {
658            frame_rate: DEFAULT_ANIMATION_FRAME_RATE,
659            tiles: Default::default(),
660        }
661    }
662}
663
664impl Deref for AnimationTiles {
665    type Target = Tiles;
666
667    fn deref(&self) -> &Self::Target {
668        &self.tiles
669    }
670}
671
672impl DerefMut for AnimationTiles {
673    fn deref_mut(&mut self) -> &mut Self::Target {
674        &mut self.tiles
675    }
676}
677
678/// A material resource plus the size of each tile, so that the tile set can
679/// carve up the material into tiles.
680#[derive(Clone, PartialEq, Debug, Visit, Reflect)]
681pub struct TileMaterial {
682    /// The source material.
683    pub material: MaterialResource,
684    /// The size of each tile in pixels.
685    pub tile_size: Vector2<u32>,
686    /// The tile data that goes along with each tile of the material.
687    pub tiles: TileGridMap<TileData>,
688}
689
690impl Deref for TileMaterial {
691    type Target = TileGridMap<TileData>;
692
693    fn deref(&self) -> &Self::Target {
694        &self.tiles
695    }
696}
697
698impl DerefMut for TileMaterial {
699    fn deref_mut(&mut self) -> &mut Self::Target {
700        &mut self.tiles
701    }
702}
703
704impl Default for TileMaterial {
705    fn default() -> Self {
706        Self {
707            material: DEFAULT_TILE_MATERIAL.deep_copy_as_embedded(),
708            tile_size: DEFAULT_TILE_SIZE,
709            tiles: TileGridMap::default(),
710        }
711    }
712}
713
714impl TileMaterial {
715    fn get_tile_bounds(&self, position: Vector2<i32>) -> Option<TileMaterialBounds> {
716        let origin = Vector2::new(
717            u32::try_from(position.x).ok()? * self.tile_size.x,
718            u32::try_from(-1 - position.y).ok()? * self.tile_size.y,
719        );
720        Some(TileMaterialBounds {
721            material: self.material.clone(),
722            bounds: TileBounds {
723                left_top_corner: origin,
724                right_top_corner: origin + Vector2::new(self.tile_size.x, 0),
725                left_bottom_corner: origin + Vector2::new(0, self.tile_size.y),
726                right_bottom_corner: origin + self.tile_size,
727            },
728        })
729    }
730    fn get_abstract_tile(&self, position: Vector2<i32>) -> Option<AbstractTile> {
731        Some(AbstractTile::Atlas(self.tiles.get(&position)?.clone()))
732    }
733    fn set_abstract_tile(
734        &mut self,
735        position: Vector2<i32>,
736        tile: Option<AbstractTile>,
737    ) -> Option<AbstractTile> {
738        if let Some(tile) = tile {
739            let AbstractTile::Atlas(data) = tile else {
740                panic!();
741            };
742            self.tiles.insert(position, data).map(AbstractTile::Atlas)
743        } else {
744            self.tiles.remove(&position).map(AbstractTile::Atlas)
745        }
746    }
747    fn get_tile_data(&self, position: Vector2<i32>) -> Option<&TileData> {
748        self.tiles.get(&position)
749    }
750    fn get_tile_data_mut(&mut self, position: Vector2<i32>) -> Option<&mut TileData> {
751        self.tiles.get_mut(&position)
752    }
753}
754
755/// Iterates through the valid handles of a tile set.
756#[derive(Default)]
757pub struct TileSetHandleIterator<'a> {
758    pages_iter: Option<hash_map::Iter<'a, Vector2<i32>, TileSetPage>>,
759    page_position: Vector2<i32>,
760    handles_iter: TileSetPageHandleIterator<'a>,
761}
762
763#[derive(Default)]
764enum TileSetPageHandleIterator<'a> {
765    #[default]
766    Empty,
767    Atlas(Keys<'a, Vector2<i32>, TileData>),
768    Freeform(Keys<'a, Vector2<i32>, TileDefinition>),
769}
770
771impl<'a> TileSetHandleIterator<'a> {
772    fn new(pages_iter: Option<hash_map::Iter<'a, Vector2<i32>, TileSetPage>>) -> Self {
773        Self {
774            pages_iter,
775            page_position: Vector2::default(),
776            handles_iter: TileSetPageHandleIterator::Empty,
777        }
778    }
779}
780
781impl Iterator for TileSetPageHandleIterator<'_> {
782    type Item = Vector2<i32>;
783
784    fn next(&mut self) -> Option<Self::Item> {
785        match self {
786            TileSetPageHandleIterator::Empty => None,
787            TileSetPageHandleIterator::Atlas(keys) => keys.next().cloned(),
788            TileSetPageHandleIterator::Freeform(keys) => keys.next().cloned(),
789        }
790    }
791}
792
793impl Iterator for TileSetHandleIterator<'_> {
794    type Item = TileDefinitionHandle;
795
796    fn next(&mut self) -> Option<Self::Item> {
797        loop {
798            if let Some(tile) = self.handles_iter.next() {
799                return TileDefinitionHandle::try_new(self.page_position, tile);
800            }
801            let (&page_position, page) = self.pages_iter.as_mut()?.next()?;
802            self.page_position = page_position;
803            self.handles_iter = match &page.source {
804                TileSetPageSource::Atlas(map) => TileSetPageHandleIterator::Atlas(map.keys()),
805                TileSetPageSource::Freeform(map) => TileSetPageHandleIterator::Freeform(map.keys()),
806                _ => TileSetPageHandleIterator::Empty,
807            };
808        }
809    }
810}
811
812/// Iterates through the positions of the tiles of a single page within a tile set.
813pub struct TileSetPaletteIterator<'a> {
814    keys: PaletteIterator<'a>,
815    page: Vector2<i32>,
816}
817
818enum PaletteIterator<'a> {
819    Empty,
820    Material(Keys<'a, Vector2<i32>, TileData>),
821    Freeform(Keys<'a, Vector2<i32>, TileDefinition>),
822    TransformSet(Keys<'a, Vector2<i32>, TileDefinitionHandle>),
823    Pages(Keys<'a, Vector2<i32>, TileSetPage>),
824}
825
826impl Iterator for TileSetPaletteIterator<'_> {
827    type Item = ResourceTilePosition;
828    fn next(&mut self) -> Option<Self::Item> {
829        match &mut self.keys {
830            PaletteIterator::Empty => None,
831            PaletteIterator::Material(iter) => iter
832                .next()
833                .map(|t| ResourceTilePosition::Tile(self.page, *t)),
834            PaletteIterator::Freeform(iter) => iter
835                .next()
836                .map(|t| ResourceTilePosition::Tile(self.page, *t)),
837            PaletteIterator::TransformSet(iter) => iter
838                .next()
839                .map(|t| ResourceTilePosition::Tile(self.page, *t)),
840            PaletteIterator::Pages(iter) => iter.next().copied().map(ResourceTilePosition::Page),
841        }
842    }
843}
844
845/// A wrapper for a [`TileSet`] resource reference that allows access to the data without panicking
846/// even if the resource is not loaded. A tile set that is not loaded acts like an empty tile set.
847pub struct TileSetRef<'a>(ResourceDataRef<'a, TileSet>);
848/// Maybe a [`TileSet`], maybe not, depending on whether the resource was successfully loaded.
849/// If it is not a `TileSet`, its methods pretend it is an empty `TileSet`.
850pub struct OptionTileSet<'a>(pub Option<&'a mut TileSet>);
851
852impl<'a> From<ResourceDataRef<'a, TileSet>> for TileSetRef<'a> {
853    fn from(value: ResourceDataRef<'a, TileSet>) -> Self {
854        Self(value)
855    }
856}
857
858impl<'a> TileSetRef<'a> {
859    /// Locks the given resource and constructs a TileSetRef using that resource, allowing
860    /// the tile set to be used without danger of panicking due to failing to load.
861    pub fn new(tile_set: &'a TileSetResource) -> Self {
862        tile_set.data_ref().into()
863    }
864    /// Borrows the underlying TileSet, if it is actually loaded.
865    #[inline]
866    pub fn as_loaded(&mut self) -> OptionTileSet {
867        OptionTileSet(self.0.as_loaded_mut())
868    }
869}
870
871impl<'a> Deref for OptionTileSet<'a> {
872    type Target = Option<&'a mut TileSet>;
873
874    fn deref(&self) -> &Self::Target {
875        &self.0
876    }
877}
878
879impl<'a> OptionTileSet<'a> {
880    /// A reference to the underlying `TileSet` if it was successfully loaded.
881    pub fn as_ref(&'a self) -> Option<&'a TileSet> {
882        self.0.as_deref()
883    }
884    /// Iterate all valid tile handles.
885    pub fn all_tiles(&self) -> TileSetHandleIterator {
886        self.as_ref().map(|t| t.all_tiles()).unwrap_or_default()
887    }
888    /// The property value for the property of the given name for the tile at the given position in this tile map.
889    /// If there is no tile data at the given handle or no property at the given UUID,
890    /// then the default value for the property's value type is returned.
891    pub fn tile_property_value<T>(
892        &self,
893        handle: TileDefinitionHandle,
894        property_id: Uuid,
895    ) -> Result<T, TilePropertyError>
896    where
897        T: TryFrom<TileSetPropertyValue, Error = TilePropertyError> + Default,
898    {
899        self.as_ref()
900            .map(|t| t.tile_property_value(handle, property_id))
901            .unwrap_or(Err(TilePropertyError::MissingTileSet))
902    }
903    /// The property value for the property of the given name for the tile at the given position in this tile map.
904    /// If there is no tile data at the given handle, then the default value for the property's value type is returned.
905    pub fn tile_property_value_by_name(
906        &self,
907        handle: TileDefinitionHandle,
908        property_name: &ImmutableString,
909    ) -> Result<TileSetPropertyValue, TilePropertyError> {
910        self.as_ref()
911            .map(|t| t.tile_property_value_by_name(handle, property_name))
912            .unwrap_or(Err(TilePropertyError::MissingTileSet))
913    }
914    /// The property value for the property of the given UUID for the tile at the given position in this tile map.
915    /// If there is no tile data at the given handle, then the default value for the property's value type is returned.
916    pub fn tile_property_value_by_uuid_untyped(
917        &self,
918        handle: TileDefinitionHandle,
919        property_id: Uuid,
920    ) -> Result<TileSetPropertyValue, TilePropertyError> {
921        self.as_ref()
922            .map(|t| t.tile_property_value_by_uuid_untyped(handle, property_id))
923            .unwrap_or(Err(TilePropertyError::MissingTileSet))
924    }
925    /// Slice containing the properties of this tile set, or an empty slice if the tile set is not loaded.
926    pub fn properties(&self) -> &[TileSetPropertyLayer] {
927        self.as_ref()
928            .map(|t| t.properties.deref())
929            .unwrap_or_default()
930    }
931    /// Slice containing the colliders of this tile set, or an empty slice if the tile set is not loaded.
932    pub fn colliders(&self) -> &[TileSetColliderLayer] {
933        self.as_ref()
934            .map(|t| t.colliders.deref())
935            .unwrap_or_default()
936    }
937    /// The color of the collider layer with the given uuid.
938    pub fn collider_color(&self, uuid: Uuid) -> Option<Color> {
939        self.as_ref()
940            .map(|t| t.collider_color(uuid))
941            .unwrap_or_default()
942    }
943    /// The collider of the given tile.
944    pub fn tile_collider(&self, handle: TileDefinitionHandle, uuid: Uuid) -> &TileCollider {
945        self.as_ref()
946            .map(|t| t.tile_collider(handle, uuid))
947            .unwrap_or_default()
948    }
949    /// The color of the given tile.
950    pub fn tile_color(&self, handle: TileDefinitionHandle) -> Option<Color> {
951        self.as_ref()
952            .map(|t| t.tile_color(handle))
953            .unwrap_or_default()
954    }
955    /// The data of the given tile.
956    pub fn tile_data(&self, handle: TileDefinitionHandle) -> Option<&TileData> {
957        self.as_ref()
958            .map(|t| t.tile_data(handle))
959            .unwrap_or_default()
960    }
961    /// The material and bounds of the given tile, if it stores its own material and bounds because it is a freeform tile.
962    pub fn tile_bounds(&self, handle: TileDefinitionHandle) -> Option<&TileMaterialBounds> {
963        self.as_ref()
964            .map(|t| t.tile_bounds(handle))
965            .unwrap_or_default()
966    }
967    /// The redirect target of the given tile. When a tile set tile does not contain its own data, but instead
968    /// it points toward a tile elsewhere in the set, this method returns the TileDefinitionHandle of that other tile.
969    pub fn tile_redirect(&self, handle: TileDefinitionHandle) -> Option<TileDefinitionHandle> {
970        self.as_ref()
971            .map(|t| t.tile_redirect(handle))
972            .unwrap_or_default()
973    }
974    /// Generate a list of all tile positions in the given page.
975    pub fn keys_on_page(&self, page: Vector2<i32>) -> Vec<Vector2<i32>> {
976        self.as_ref()
977            .map(|t| t.keys_on_page(page))
978            .unwrap_or_default()
979    }
980    /// Generate a list of all page positions.
981    pub fn page_keys(&self) -> Vec<Vector2<i32>> {
982        self.as_ref().map(|t| t.page_keys()).unwrap_or_default()
983    }
984    /// True if there is a tile at the given position on the given page.
985    pub fn has_tile_at(&self, page: Vector2<i32>, tile: Vector2<i32>) -> bool {
986        self.as_ref()
987            .map(|t| t.has_tile_at(page, tile))
988            .unwrap_or_default()
989    }
990    /// The handle of the icon that represents the given page.
991    pub fn page_icon(&self, page: Vector2<i32>) -> Option<TileDefinitionHandle> {
992        self.as_ref().map(|t| t.page_icon(page)).unwrap_or_default()
993    }
994    /// Get the UUID of the property with the given name, if that property exists.
995    pub fn property_name_to_uuid(&self, name: &ImmutableString) -> Option<Uuid> {
996        self.as_ref()
997            .map(|t| t.property_name_to_uuid(name))
998            .unwrap_or_default()
999    }
1000    /// Get the UUID of the collider with the given name, if that collider exists.
1001    pub fn collider_name_to_uuid(&self, name: &ImmutableString) -> Option<Uuid> {
1002        self.as_ref()
1003            .map(|t| t.collider_name_to_uuid(name))
1004            .unwrap_or_default()
1005    }
1006    /// Find a property layer by its UUID.
1007    pub fn find_property(&self, uuid: Uuid) -> Option<&TileSetPropertyLayer> {
1008        self.as_ref()
1009            .map(|t| t.find_property(uuid))
1010            .unwrap_or_default()
1011    }
1012    /// Find a collider layer by its UUID.
1013    pub fn find_collider(&self, uuid: Uuid) -> Option<&TileSetColliderLayer> {
1014        self.as_ref()
1015            .map(|t| t.find_collider(uuid))
1016            .unwrap_or_default()
1017    }
1018    /// Find every transform set tile handle in this set. A transform set tile reference is a tile
1019    /// with data that includes `transform_tile` with some value.
1020    /// The given function will be called as `func(source_tile, transform_tile)` where
1021    /// `source_tile` is the handle of the tile containing the data.
1022    pub fn rebuild_transform_sets(&mut self) {
1023        if let Some(t) = &mut self.0 {
1024            t.rebuild_transform_sets()
1025        }
1026    }
1027    /// Iterate through the tiles of every animation page and establish the connection between
1028    /// the tiles of other pages and their corresponding position in an animation page.
1029    /// This should happen after any animation page is changed and before it is next used.
1030    pub fn rebuild_animations(&mut self) {
1031        if let Some(t) = &mut self.0 {
1032            t.rebuild_animations()
1033        }
1034    }
1035    /// Find a texture from some material page to serve as a preview for the tile set.
1036    pub fn preview_texture(&self) -> Option<TextureResource> {
1037        self.as_ref()
1038            .map(|t| t.preview_texture())
1039            .unwrap_or_default()
1040    }
1041    /// Get the page at the given position.
1042    pub fn get_page(&self, position: Vector2<i32>) -> Option<&TileSetPage> {
1043        self.as_ref()
1044            .map(|t| t.get_page(position))
1045            .unwrap_or_default()
1046    }
1047    /// Get the page at the given position.
1048    pub fn get_page_mut(&mut self, position: Vector2<i32>) -> Option<&mut TileSetPage> {
1049        self.0
1050            .as_mut()
1051            .map(|t| t.get_page_mut(position))
1052            .unwrap_or_default()
1053    }
1054    /// Returns true if the given handle points to a tile definition.
1055    pub fn is_valid_tile(&self, handle: TileDefinitionHandle) -> bool {
1056        self.as_ref()
1057            .map(|t| t.is_valid_tile(handle))
1058            .unwrap_or_default()
1059    }
1060    /// The tile at the given page and tile coordinates.
1061    pub fn get_abstract_tile(
1062        &self,
1063        page: Vector2<i32>,
1064        tile: Vector2<i32>,
1065    ) -> Option<AbstractTile> {
1066        self.as_ref()
1067            .map(|t| t.get_abstract_tile(page, tile))
1068            .unwrap_or_default()
1069    }
1070    /// The render data for the tile at the given handle after applying the transform.
1071    pub fn get_transformed_render_data(
1072        &self,
1073        trans: OrthoTransformation,
1074        handle: TileDefinitionHandle,
1075    ) -> Option<TileRenderData> {
1076        self.as_ref()
1077            .map(|t| t.get_transformed_render_data(trans, handle))
1078            .unwrap_or_else(|| Some(TileRenderData::missing_data()))
1079    }
1080    /// Return the `TileRenderData` needed to render the tile at the given handle.
1081    /// The handle is redirected if it refers to a reference to another tile.
1082    /// If a reference is redirected and the resulting handle does not point to a tile definition,
1083    /// then `TileRenderData::missing_tile()` is returned to that an error tile will be rendered.
1084    /// If the given handle does not point to a reference and it does not point to a tile definition,
1085    /// then None is returned since nothing should be rendered.
1086    pub fn get_tile_render_data(&self, position: ResourceTilePosition) -> Option<TileRenderData> {
1087        self.as_ref()
1088            .map(|t| t.get_tile_render_data(position))
1089            .unwrap_or_else(|| Some(TileRenderData::missing_data()))
1090    }
1091    /// The tile collider with the given UUID for the tile at the given handle.
1092    pub fn get_tile_collider(
1093        &self,
1094        handle: TileDefinitionHandle,
1095        uuid: Uuid,
1096    ) -> Option<&TileCollider> {
1097        self.as_ref()
1098            .map(|t| t.get_tile_collider(handle, uuid))
1099            .unwrap_or_default()
1100    }
1101    /// Loop through the tiles of the given page and find the render data for each tile,
1102    /// then passes it to the given function.
1103    pub fn palette_render_loop<F>(&self, stage: TilePaletteStage, page: Vector2<i32>, func: F)
1104    where
1105        F: FnMut(Vector2<i32>, TileRenderData),
1106    {
1107        if let Some(tile_set) = &self.0 {
1108            tile_set.palette_render_loop(stage, page, func);
1109        }
1110    }
1111    /// Loop through the tiles of the given page and find each of the tile colliders on each tile,
1112    /// then pass the collider to the given function along with the collider's UUID and color.
1113    pub fn tile_collider_loop<F>(&self, page: Vector2<i32>, func: F)
1114    where
1115        F: FnMut(Vector2<i32>, Uuid, Color, &TileCollider),
1116    {
1117        if let Some(tile_set) = &self.0 {
1118            tile_set.tile_collider_loop(page, func);
1119        }
1120    }
1121
1122    /// Some tiles in a tile set are references to tiles elsewhere in the tile set.
1123    /// In particular, the tiles of a transform set page all contain references to other pages,
1124    /// and the page tiles are also references. If this method is given the position of one of these
1125    /// reference tiles, then it returns the handle of the referenced tile.
1126    /// If the given position points to a tile without a redirect, then the tile's handle is returned.
1127    /// If the given position points to a non-existent page or a non-existent tile, then None is returned.
1128    pub fn redirect_handle(&self, position: ResourceTilePosition) -> Option<TileDefinitionHandle> {
1129        self.as_ref()
1130            .map(|t| t.redirect_handle(position))
1131            .unwrap_or_default()
1132    }
1133    /// If the given handle refers to a transform set page, find the tile on that page and return the handle of wherever
1134    /// the tile comes from originally. Return None if the page is not a transform set or there is no tile at that position.
1135    pub fn get_transform_tile_source(
1136        &self,
1137        handle: TileDefinitionHandle,
1138    ) -> Option<TileDefinitionHandle> {
1139        self.as_ref()
1140            .map(|t| t.get_transform_tile_source(handle))
1141            .unwrap_or_default()
1142    }
1143    /// Returns a clone of the full definition of the tile at the given handle, if possible.
1144    /// Use [TileSet::get_tile_data] if a clone is not needed.
1145    pub fn get_definition(&self, handle: TileDefinitionHandle) -> Option<TileDefinition> {
1146        self.as_ref()
1147            .map(|t| t.get_definition(handle))
1148            .unwrap_or_default()
1149    }
1150    /// Return a copy of the definition of the tile at the given handle with the given transformation applied.
1151    pub fn get_transformed_definition(
1152        &self,
1153        trans: OrthoTransformation,
1154        handle: TileDefinitionHandle,
1155    ) -> Option<TileDefinition> {
1156        self.as_ref()
1157            .map(|t| t.get_transformed_definition(trans, handle))
1158            .unwrap_or_default()
1159    }
1160    /// Get the tile definition at the given position.
1161    pub fn get_tile_bounds(&self, position: ResourceTilePosition) -> Option<TileMaterialBounds> {
1162        self.as_ref()
1163            .map(|t| t.get_tile_bounds(position))
1164            .unwrap_or_default()
1165    }
1166    /// The value of the property with the given UUID for the given tile.
1167    pub fn property_value(
1168        &self,
1169        handle: TileDefinitionHandle,
1170        property_id: Uuid,
1171    ) -> Option<TileSetPropertyValue> {
1172        self.as_ref()
1173            .map(|t| t.property_value(handle, property_id))
1174            .unwrap_or_default()
1175    }
1176    /// Get the tile data at the given position.
1177    pub fn get_tile_data(&self, position: ResourceTilePosition) -> Option<&TileData> {
1178        self.as_ref()
1179            .map(|t| t.get_tile_data(position))
1180            .unwrap_or_default()
1181    }
1182    /// Get the tile data at the given position.
1183    pub fn get_tile_data_mut(&mut self, handle: TileDefinitionHandle) -> Option<&mut TileData> {
1184        self.0
1185            .as_mut()
1186            .map(|t| t.get_tile_data_mut(handle))
1187            .unwrap_or_default()
1188    }
1189
1190    /// Finds the handle of the tile that represents a transformed version of the tile at the given handle, if such a tile exists.
1191    /// The given tile needs to have a `transform_tile` in its data, that handle needs to point to a transform set page,
1192    /// and that page needs to have a tile in the position corresponding to the desired transform relative to the `transform_tile` position.
1193    /// All 8 possible transforms are grouped together in 4x2 rectangles within each transform set page, and every transformation is possible
1194    /// so long as all 8 cells are filled with tiles. Otherwise, None is returned.
1195    pub fn get_transformed_version(
1196        &self,
1197        transform: OrthoTransformation,
1198        handle: TileDefinitionHandle,
1199    ) -> Option<TileDefinitionHandle> {
1200        self.as_ref()
1201            .map(|t| t.get_transformed_version(transform, handle))
1202            .unwrap_or_default()
1203    }
1204
1205    /// The handle of the tile in the animation sequence starting from the given tile handle
1206    /// at the given time, or none if the given handle is not part of any animation sequence.
1207    pub fn get_animated_version(
1208        &self,
1209        time: f32,
1210        handle: TileDefinitionHandle,
1211    ) -> Option<TileDefinitionHandle> {
1212        self.as_ref()
1213            .map(|t| t.get_animated_version(time, handle))
1214            .unwrap_or_default()
1215    }
1216
1217    /// Get the tile definition handles for all of the given coordinates on the given page.
1218    pub fn get_tiles<I: Iterator<Item = Vector2<i32>>>(
1219        &self,
1220        stage: TilePaletteStage,
1221        page: Vector2<i32>,
1222        iter: I,
1223        tiles: &mut Tiles,
1224    ) {
1225        if let Some(tile_set) = &self.0 {
1226            tile_set.get_tiles(stage, page, iter, tiles);
1227        }
1228    }
1229    /// The bounding rect of the pages.
1230    pub fn pages_bounds(&self) -> OptionTileRect {
1231        self.as_ref().map(|t| t.pages_bounds()).unwrap_or_default()
1232    }
1233    /// The bounding rect of the tiles of the given page.
1234    pub fn tiles_bounds(&self, stage: TilePaletteStage, page: Vector2<i32>) -> OptionTileRect {
1235        self.as_ref()
1236            .map(|t| t.tiles_bounds(stage, page))
1237            .unwrap_or_default()
1238    }
1239
1240    /// Returns true if the tile set is unoccupied at the given position.
1241    pub fn is_free_at(&self, position: ResourceTilePosition) -> bool {
1242        self.as_ref()
1243            .map(|t| t.is_free_at(position))
1244            .unwrap_or(true)
1245    }
1246}
1247
1248/// The index of an animation within the animation list, and an offset
1249/// to indicate where we should start playing within the animation.
1250#[derive(Debug, Default, Clone)]
1251struct AnimationRef {
1252    index: usize,
1253    offset: i32,
1254}
1255
1256/// The position and length of an animation within some animation page.
1257#[derive(Debug, Default, Clone)]
1258struct Animation {
1259    start: TileDefinitionHandle,
1260    length: i32,
1261}
1262
1263impl Animation {
1264    fn iter(&self) -> impl Iterator<Item = (TileDefinitionHandle, i32)> {
1265        let page = self.start.page();
1266        let tile = self.start.tile();
1267        (0..self.length).filter_map(move |i| {
1268            let x = tile.x.saturating_add(i);
1269            let handle = TileDefinitionHandle::try_new(page, Vector2::new(x, tile.y))?;
1270            Some((handle, i))
1271        })
1272    }
1273    fn page(&self) -> Vector2<i32> {
1274        self.start.page()
1275    }
1276    fn frame(&self, frame: i32) -> Vector2<i32> {
1277        let tile = self.start.tile();
1278        Vector2::new(tile.x + frame.rem_euclid(self.length), tile.y)
1279    }
1280}
1281
1282/// A lookup table to locate animations within a tile set.
1283#[derive(Debug, Default, Clone)]
1284struct AnimationCache {
1285    handle_to_animation: FxHashMap<TileDefinitionHandle, AnimationRef>,
1286    animations: Vec<Animation>,
1287}
1288
1289impl AnimationCache {
1290    fn clear(&mut self) {
1291        self.handle_to_animation.clear();
1292        self.animations.clear();
1293    }
1294    fn add_animation(&mut self, start: TileDefinitionHandle, length: i32) {
1295        self.animations.push(Animation { start, length });
1296    }
1297    fn get_animation_and_offset(&self, handle: TileDefinitionHandle) -> Option<(&Animation, i32)> {
1298        let AnimationRef { index, offset } = self.handle_to_animation.get(&handle)?;
1299        let animation = self.animations.get(*index)?;
1300        Some((animation, *offset))
1301    }
1302}
1303
1304/// Tile set is a special storage for tile descriptions. It is a sort of database, that contains
1305/// descriptions (definitions) for tiles. Such approach allows you to change appearance of all tiles
1306/// of particular kind at once.
1307///
1308/// The tile data of the tile set is divided into pages, and pages come in three different types depending
1309/// on what kind of data is stored on the page. See [`TileSetPage`] for more information about the
1310/// page variants.
1311///
1312/// A tile set also contains extra layers of data that may be included with each tile:
1313/// Collider layers and property layers.
1314///
1315/// A *property layer* allows a particular value to be assigned to each tile.
1316/// The each layer has a name and a data type for the value, and it may optionally have
1317/// a list of pre-defined values and names for each of the pre-defined values.
1318/// This makes it easier to keep track of values which may have special meanings
1319/// when editing the tile set. See [`TileSetPropertyLayer`] for more information.
1320///
1321/// A *collider layer* allows a shape to be assigned to each tile for the purpose of
1322/// constructing a physics object for the tile map.
1323/// Each layer has a name and a color. The color is used to allow the user to quickly
1324/// identify which shapes correspond to which layers while in the tile set editor.
1325/// See [`TileSetColliderLayer`] for more information.
1326#[derive(Clone, Default, Debug, Visit, Reflect, TypeUuidProvider, ComponentProvider)]
1327#[type_uuid(id = "7b7e057b-a41e-4150-ab3b-0ae99f4024f0")]
1328pub struct TileSet {
1329    /// A mapping from animated tiles to the corresponding cells on animation pages.
1330    #[reflect(hidden)]
1331    #[visit(skip)]
1332    animation_map: AnimationCache,
1333    /// A mapping from transformable tiles to the corresponding cells on transform set pages.
1334    #[reflect(hidden)]
1335    #[visit(skip)]
1336    transform_map: FxHashMap<TileDefinitionHandle, TileDefinitionHandle>,
1337    /// The set of pages, organized by position.
1338    pub pages: FxHashMap<Vector2<i32>, TileSetPage>,
1339    /// Collider layers, in the order in which the layers should be presented in the editor.
1340    pub colliders: Vec<TileSetColliderLayer>,
1341    /// Property types in the order in which the layers should be presented in the editor.
1342    pub properties: Vec<TileSetPropertyLayer>,
1343    /// A count of changes since last save. New changes add +1. Reverting to previous
1344    /// states add -1. Reverting to a state before the last save can result in negative
1345    /// values. Saving is unnecessary whenever this value is 0.
1346    #[reflect(hidden)]
1347    #[visit(skip)]
1348    pub change_flag: ChangeFlag,
1349}
1350
1351impl TileSet {
1352    /// The stamp element for the given position, if the tile in that cell is used
1353    /// to create a stamp. The [`StampElement::handle`] refers to the location of the tile within the
1354    /// tile set, while the [`StampElement::source`] refers to the location of the tile within
1355    /// the brush.
1356    pub fn stamp_element(&self, position: ResourceTilePosition) -> Option<StampElement> {
1357        self.redirect_handle(position).map(|handle| StampElement {
1358            handle,
1359            source: Some(position),
1360        })
1361    }
1362
1363    /// Iterate all valid tile handles.
1364    pub fn all_tiles(&self) -> TileSetHandleIterator {
1365        TileSetHandleIterator::new(Some(self.pages.iter()))
1366    }
1367    /// The property value for the property of the given name for the tile at the given position in this tile map.
1368    /// If there is no tile data at the given handle or no property at the given UUID,
1369    /// then the default value for the property's value type is returned.
1370    pub fn tile_property_value<T>(
1371        &self,
1372        handle: TileDefinitionHandle,
1373        property_id: Uuid,
1374    ) -> Result<T, TilePropertyError>
1375    where
1376        T: TryFrom<TileSetPropertyValue, Error = TilePropertyError> + Default,
1377    {
1378        self.property_value(handle, property_id)
1379            .map(T::try_from)
1380            .unwrap_or_else(|| Ok(T::default()))
1381    }
1382    /// The property value for the property of the given name for the tile at the given position in this tile map.
1383    /// If there is no tile data at the given handle, then the default value for the property's value type is returned.
1384    pub fn tile_property_value_by_name(
1385        &self,
1386        handle: TileDefinitionHandle,
1387        property_name: &ImmutableString,
1388    ) -> Result<TileSetPropertyValue, TilePropertyError> {
1389        let property = self
1390            .find_property_by_name(property_name)
1391            .ok_or_else(|| TilePropertyError::UnrecognizedName(property_name.clone()))?;
1392        Ok(self
1393            .property_value(handle, property.uuid)
1394            .unwrap_or_else(|| property.prop_type.default_value()))
1395    }
1396    /// The property value for the property of the given UUID for the tile at the given position in this tile map.
1397    /// If there is no tile data at the given handle, then the default value for the property's value type is returned.
1398    pub fn tile_property_value_by_uuid_untyped(
1399        &self,
1400        handle: TileDefinitionHandle,
1401        property_id: Uuid,
1402    ) -> Result<TileSetPropertyValue, TilePropertyError> {
1403        if let Some(value) = self.property_value(handle, property_id) {
1404            Ok(value)
1405        } else {
1406            let property = self
1407                .find_property(property_id)
1408                .ok_or(TilePropertyError::UnrecognizedUuid(property_id))?;
1409            Ok(property.prop_type.default_value())
1410        }
1411    }
1412    /// The color of the collider layer with the given uuid.
1413    pub fn collider_color(&self, uuid: Uuid) -> Option<Color> {
1414        self.find_collider(uuid).map(|layer| layer.color)
1415    }
1416    /// The collider of the given tile.
1417    pub fn tile_collider(&self, handle: TileDefinitionHandle, uuid: Uuid) -> &TileCollider {
1418        let Some(data) = self.tile_data(handle) else {
1419            return &TileCollider::None;
1420        };
1421        data.colliders.get(&uuid).unwrap_or(&TileCollider::None)
1422    }
1423    /// The color of the given tile.
1424    pub fn tile_color(&self, handle: TileDefinitionHandle) -> Option<Color> {
1425        self.tile_data(handle).map(|d| d.color)
1426    }
1427    /// The data of the given tile.
1428    pub fn tile_data(&self, handle: TileDefinitionHandle) -> Option<&TileData> {
1429        let page_source = self.pages.get(&handle.page()).map(|p| &p.source)?;
1430        match page_source {
1431            TileSetPageSource::Atlas(m) => m.get(&handle.tile()),
1432            TileSetPageSource::Freeform(m) => m.get(&handle.tile()).map(|def| &def.data),
1433            TileSetPageSource::Transform(_) => None,
1434            TileSetPageSource::Animation(_) => None,
1435        }
1436    }
1437    /// The material and bounds of the given tile, if it stores its own material and bounds because it is a freeform tile.
1438    pub fn tile_bounds(&self, handle: TileDefinitionHandle) -> Option<&TileMaterialBounds> {
1439        let page_source = self.pages.get(&handle.page()).map(|p| &p.source)?;
1440        if let TileSetPageSource::Freeform(m) = page_source {
1441            m.get(&handle.tile()).map(|t| &t.material_bounds)
1442        } else {
1443            None
1444        }
1445    }
1446    /// The redirect target of the given tile. When a tile set tile does not contain its own data, but instead
1447    /// it points toward a tile elsewhere in the set, this method returns the TileDefinitionHandle of that other tile.
1448    pub fn tile_redirect(&self, handle: TileDefinitionHandle) -> Option<TileDefinitionHandle> {
1449        let page_source = self.pages.get(&handle.page()).map(|p| &p.source)?;
1450        match page_source {
1451            TileSetPageSource::Transform(m) => m.get(&handle.tile()).copied(),
1452            TileSetPageSource::Animation(m) => m.get(&handle.tile()).copied(),
1453            _ => None,
1454        }
1455    }
1456    /// Generate a list of all tile positions in the given page.
1457    pub fn keys_on_page(&self, page: Vector2<i32>) -> Vec<Vector2<i32>> {
1458        self.pages.get(&page).map(|p| p.keys()).unwrap_or_default()
1459    }
1460    /// Generate a list of all page positions.
1461    pub fn page_keys(&self) -> Vec<Vector2<i32>> {
1462        self.pages.keys().copied().collect()
1463    }
1464    /// True if there is a tile at the given position on the given page.
1465    pub fn has_tile_at(&self, page: Vector2<i32>, tile: Vector2<i32>) -> bool {
1466        let Some(page) = self.pages.get(&page).map(|p| &p.source) else {
1467            return false;
1468        };
1469        match page {
1470            TileSetPageSource::Atlas(m) => m.contains_key(&tile),
1471            TileSetPageSource::Freeform(m) => m.contains_key(&tile),
1472            TileSetPageSource::Transform(m) => m.contains_key(&tile),
1473            TileSetPageSource::Animation(m) => m.contains_key(&tile),
1474        }
1475    }
1476    /// The handle of the icon that represents the given page.
1477    pub fn page_icon(&self, page: Vector2<i32>) -> Option<TileDefinitionHandle> {
1478        self.pages.get(&page).map(|p| p.icon)
1479    }
1480    /// Get the UUID of the property with the given name, if that property exists.
1481    pub fn property_name_to_uuid(&self, name: &ImmutableString) -> Option<Uuid> {
1482        self.find_property_by_name(name).map(|p| p.uuid)
1483    }
1484    /// Get the UUID of the collider with the given name, if that collider exists.
1485    pub fn collider_name_to_uuid(&self, name: &ImmutableString) -> Option<Uuid> {
1486        self.colliders
1487            .iter()
1488            .find(|c| &c.name == name)
1489            .map(|c| c.uuid)
1490    }
1491    /// Find a property layer by its name.
1492    pub fn find_property_by_name(
1493        &self,
1494        property_name: &ImmutableString,
1495    ) -> Option<&TileSetPropertyLayer> {
1496        self.properties.iter().find(|p| &p.name == property_name)
1497    }
1498    /// Find a property layer by its UUID.
1499    pub fn find_property(&self, uuid: Uuid) -> Option<&TileSetPropertyLayer> {
1500        self.properties.iter().find(|p| p.uuid == uuid)
1501    }
1502    /// Find a property layer by its UUID.
1503    pub fn find_property_mut(&mut self, uuid: Uuid) -> Option<&mut TileSetPropertyLayer> {
1504        self.properties.iter_mut().find(|p| p.uuid == uuid)
1505    }
1506    /// Find a collider layer by its UUID.
1507    pub fn find_collider(&self, uuid: Uuid) -> Option<&TileSetColliderLayer> {
1508        self.colliders.iter().find(|p| p.uuid == uuid)
1509    }
1510    /// Find a collider layer by its UUID.
1511    pub fn find_collider_mut(&mut self, uuid: Uuid) -> Option<&mut TileSetColliderLayer> {
1512        self.colliders.iter_mut().find(|p| p.uuid == uuid)
1513    }
1514    /// Iterate through the tiles of every transform set page and establish the connection between
1515    /// the tiles of other pages and their corresponding position in a transform set page.
1516    /// This should happen after any transform set is changed and before it is next used.
1517    pub fn rebuild_transform_sets(&mut self) {
1518        self.transform_map.clear();
1519        for (&position, page) in self.pages.iter_mut() {
1520            let TileSetPageSource::Transform(tiles) = &page.source else {
1521                continue;
1522            };
1523            self.transform_map.extend(
1524                tiles
1525                    .iter()
1526                    .filter_map(|(&k, &v)| Some((v, TileDefinitionHandle::try_new(position, k)?))),
1527            );
1528        }
1529    }
1530    /// Iterate through the tiles of every animation page and establish the connection between
1531    /// the tiles of other pages and their corresponding position in an animation page.
1532    /// This should happen after any animation page is changed and before it is next used.
1533    ///
1534    /// If a tile appears in multiple animation pages, the pages with greater y-coordinate
1535    /// are prioritized, followed by prioritizing lower x-coordinate. If a tile appears
1536    /// more than once on the same page, the cell with greater y-coordinate is prioritized,
1537    /// followed by lower x-coordinate. In this way a unique animation is always chosen for
1538    /// every tile that appears on any animation page.
1539    pub fn rebuild_animations(&mut self) {
1540        self.animation_map.clear();
1541        for (&position, page) in self.pages.iter_mut() {
1542            let TileSetPageSource::Animation(tiles) = &page.source else {
1543                continue;
1544            };
1545            for &k in tiles.keys() {
1546                let left = Vector2::new(k.x - 1, k.y);
1547                if tiles.contains_key(&left) {
1548                    continue;
1549                }
1550                let mut right = Vector2::new(k.x + 1, k.y);
1551                while tiles.contains_key(&right) {
1552                    right.x += 1;
1553                }
1554                let Some(start) = TileDefinitionHandle::try_new(position, k) else {
1555                    continue;
1556                };
1557                let length = right.x - k.x;
1558                self.animation_map.add_animation(start, length);
1559            }
1560        }
1561        for (index, animation) in self.animation_map.animations.iter().enumerate() {
1562            let page = self.pages.get(&animation.page()).unwrap();
1563            let TileSetPageSource::Animation(tiles) = &page.source else {
1564                unreachable!();
1565            };
1566            for (handle, offset) in animation.iter() {
1567                let handle = *tiles.get(&handle.tile()).unwrap();
1568                let anim_ref = AnimationRef { index, offset };
1569                match self.animation_map.handle_to_animation.entry(handle) {
1570                    Entry::Occupied(mut entry) => {
1571                        let prev = entry.get();
1572                        if offset == 0 && prev.offset != 0 {
1573                            entry.insert(anim_ref);
1574                        } else if (offset == 0) == (prev.offset == 0) {
1575                            let new_start = animation.start;
1576                            let prev_start = self.animation_map.animations[prev.index].start;
1577                            if new_start < prev_start {
1578                                entry.insert(anim_ref);
1579                            }
1580                        }
1581                    }
1582                    Entry::Vacant(entry) => drop(entry.insert(anim_ref)),
1583                }
1584            }
1585        }
1586    }
1587    /// Find a texture from some material page to serve as a preview for the tile set.
1588    pub fn preview_texture(&self) -> Option<TextureResource> {
1589        self.pages
1590            .iter()
1591            .filter_map(|(&pos, p)| match &p.source {
1592                TileSetPageSource::Atlas(mat) => {
1593                    Some((pos, mat.material.state().data()?.texture("diffuseTexture")?))
1594                }
1595                _ => None,
1596            })
1597            .min_by(|(a, _), (b, _)| a.y.cmp(&b.y).reverse().then(a.x.cmp(&b.x)))
1598            .map(|(_, texture)| texture)
1599    }
1600    /// Update the tile set using data stored in the given `TileSetUpdate`
1601    /// and modify the `TileSetUpdate` to become the reverse of the changes by storing
1602    /// the data that was removed from this TileSet.
1603    ///
1604    /// [`rebuild_transform_sets`](Self::rebuild_transform_sets) is automatically called if a transform set page is
1605    /// modified.
1606    /// [`rebuild_animations`](Self::rebuild_animations) is automatically called if an animation page is
1607    /// modified.
1608    ///
1609    /// Wherever there is incompatibility between the tile set and the given update,
1610    /// the tile set should gracefully ignore that part of the update, log the error,
1611    /// and set the update to [`TileDataUpdate::DoNothing`], because that is the correct
1612    /// reversal of nothing being done.
1613    pub fn swap(&mut self, update: &mut TileSetUpdate) {
1614        let mut transform_changes = false;
1615        let mut animation_changes: bool = false;
1616        for (handle, tile_update) in update.iter_mut() {
1617            let Some(page) = self.pages.get_mut(&handle.page()) else {
1618                Log::err("Tile set update page missing.");
1619                continue;
1620            };
1621            if page.is_transform_set() {
1622                transform_changes = true;
1623            }
1624            if page.is_animation() {
1625                animation_changes = true;
1626            }
1627            page.swap_tile(handle.tile(), tile_update);
1628        }
1629        if transform_changes {
1630            self.rebuild_transform_sets();
1631        }
1632        if animation_changes {
1633            self.rebuild_animations();
1634        }
1635    }
1636    /// Get the page at the given position.
1637    pub fn get_page(&self, position: Vector2<i32>) -> Option<&TileSetPage> {
1638        self.pages.get(&position)
1639    }
1640    /// Get the page at the given position.
1641    pub fn get_page_mut(&mut self, position: Vector2<i32>) -> Option<&mut TileSetPage> {
1642        self.pages.get_mut(&position)
1643    }
1644    /// Insert the given page at the given position.
1645    pub fn insert_page(
1646        &mut self,
1647        position: Vector2<i32>,
1648        page: TileSetPage,
1649    ) -> Option<TileSetPage> {
1650        self.pages.insert(position, page)
1651    }
1652    /// Remove the page at the given position, if there is a page at that position.
1653    pub fn remove_page(&mut self, position: Vector2<i32>) -> Option<TileSetPage> {
1654        self.pages.remove(&position)
1655    }
1656    /// Returns true if the given handle points to a tile definition.
1657    pub fn is_valid_tile(&self, handle: TileDefinitionHandle) -> bool {
1658        let Some(source) = self.pages.get(&handle.page()).map(|p| &p.source) else {
1659            return false;
1660        };
1661        match source {
1662            TileSetPageSource::Atlas(mat) => mat.contains_key(&handle.tile()),
1663            TileSetPageSource::Freeform(map) => map.contains_key(&handle.tile()),
1664            TileSetPageSource::Transform(_) => false,
1665            TileSetPageSource::Animation(_) => false,
1666        }
1667    }
1668    /// The tile at the given page and tile coordinates.
1669    pub fn get_abstract_tile(
1670        &self,
1671        page: Vector2<i32>,
1672        tile: Vector2<i32>,
1673    ) -> Option<AbstractTile> {
1674        let source = self.pages.get(&page).map(|p| &p.source)?;
1675        match source {
1676            TileSetPageSource::Atlas(tile_material) => tile_material.get_abstract_tile(tile),
1677            TileSetPageSource::Freeform(tile_grid_map) => {
1678                Some(AbstractTile::Freeform(tile_grid_map.get(&tile)?.clone()))
1679            }
1680            TileSetPageSource::Transform(tiles) => {
1681                Some(AbstractTile::Transform(tiles.get(&tile).copied()?))
1682            }
1683            TileSetPageSource::Animation(tiles) => {
1684                Some(AbstractTile::Transform(tiles.get(&tile).copied()?))
1685            }
1686        }
1687    }
1688    /// Put the given tile into the cell at the given page and tile coordinates.
1689    pub fn set_abstract_tile(
1690        &mut self,
1691        page: Vector2<i32>,
1692        tile: Vector2<i32>,
1693        value: Option<AbstractTile>,
1694    ) -> Option<AbstractTile> {
1695        let Some(source) = self.pages.get_mut(&page).map(|p| &mut p.source) else {
1696            panic!();
1697        };
1698        use AbstractTile as Tile;
1699        use TileSetPageSource as Source;
1700        match (source, value) {
1701            (Source::Atlas(d0), value) => d0.set_abstract_tile(tile, value),
1702            (Source::Freeform(d0), Some(Tile::Freeform(d1))) => {
1703                Some(Tile::Freeform(d0.insert(tile, d1)?))
1704            }
1705            (Source::Freeform(d0), None) => Some(Tile::Freeform(d0.remove(&tile)?)),
1706            (Source::Transform(d0), Some(Tile::Transform(d1))) => {
1707                d0.insert(tile, d1).map(Tile::Transform)
1708            }
1709            (Source::Transform(d0), None) => d0.remove(&tile).map(Tile::Transform),
1710            (Source::Animation(d0), Some(Tile::Transform(d1))) => {
1711                d0.insert(tile, d1).map(Tile::Transform)
1712            }
1713            (Source::Animation(d0), None) => d0.remove(&tile).map(Tile::Transform),
1714            _ => panic!(),
1715        }
1716    }
1717    /// The render data for the tile at the given handle after applying the transform.
1718    pub fn get_transformed_render_data(
1719        &self,
1720        trans: OrthoTransformation,
1721        handle: TileDefinitionHandle,
1722    ) -> Option<TileRenderData> {
1723        if handle.is_empty() {
1724            Some(TileRenderData::empty())
1725        } else if let Some(handle) = self.get_transformed_version(trans, handle) {
1726            self.get_tile_render_data(handle.into())
1727        } else {
1728            Some(self.get_tile_render_data(handle.into())?.transformed(trans))
1729        }
1730    }
1731    /// Return the `TileRenderData` needed to render the tile at the given handle.
1732    /// The handle is redirected if it refers to a reference to another tile.
1733    /// If a reference is redirected and the resulting handle does not point to a tile definition,
1734    /// then `TileRenderData::missing_tile()` is returned to that an error tile will be rendered.
1735    /// If the given handle does not point to a reference and it does not point to a tile definition,
1736    /// then None is returned since nothing should be rendered.
1737    pub fn get_tile_render_data(&self, position: ResourceTilePosition) -> Option<TileRenderData> {
1738        if self.is_free_at(position) {
1739            return None;
1740        }
1741        self.inner_get_render_data(position)
1742            .or_else(|| Some(TileRenderData::missing_data()))
1743    }
1744    fn inner_get_render_data(&self, position: ResourceTilePosition) -> Option<TileRenderData> {
1745        let handle = self.redirect_handle(position)?;
1746        if handle.is_empty() {
1747            Some(TileRenderData::empty())
1748        } else if self.is_valid_tile(handle) {
1749            Some(TileRenderData::new(
1750                Some(self.get_tile_bounds(position)?),
1751                self.get_tile_data(position)?.color,
1752            ))
1753        } else {
1754            Some(TileRenderData::missing_data())
1755        }
1756    }
1757    /// The tile collider with the given UUID for the tile at the given handle.
1758    pub fn get_tile_collider(
1759        &self,
1760        handle: TileDefinitionHandle,
1761        uuid: Uuid,
1762    ) -> Option<&TileCollider> {
1763        if self.is_free_at(handle.into()) {
1764            return None;
1765        }
1766        let handle = self.redirect_handle(handle.into())?;
1767        let data = self.get_tile_data(handle.into())?;
1768        data.colliders.get(&uuid)
1769    }
1770    /// An iterator over the `TileDefinitionHandle` of each tile on the given page.
1771    pub fn palette_iterator(
1772        &self,
1773        stage: TilePaletteStage,
1774        page: Vector2<i32>,
1775    ) -> TileSetPaletteIterator {
1776        TileSetPaletteIterator {
1777            page,
1778            keys: self.palette_keys(stage, page),
1779        }
1780    }
1781    fn palette_keys(&self, stage: TilePaletteStage, page: Vector2<i32>) -> PaletteIterator {
1782        match stage {
1783            TilePaletteStage::Pages => PaletteIterator::Pages(self.pages.keys()),
1784            TilePaletteStage::Tiles => {
1785                let Some(page) = self.pages.get(&page) else {
1786                    return PaletteIterator::Empty;
1787                };
1788                match &page.source {
1789                    TileSetPageSource::Atlas(mat) => PaletteIterator::Material(mat.tiles.keys()),
1790                    TileSetPageSource::Freeform(map) => PaletteIterator::Freeform(map.keys()),
1791                    TileSetPageSource::Transform(tiles) => {
1792                        PaletteIterator::TransformSet(tiles.keys())
1793                    }
1794                    TileSetPageSource::Animation(tiles) => {
1795                        PaletteIterator::TransformSet(tiles.keys())
1796                    }
1797                }
1798            }
1799        }
1800    }
1801
1802    /// Loop through the tiles of the given page and find the render data for each tile,
1803    /// then passes it to the given function.
1804    pub fn palette_render_loop<F>(&self, stage: TilePaletteStage, page: Vector2<i32>, mut func: F)
1805    where
1806        F: FnMut(Vector2<i32>, TileRenderData),
1807    {
1808        for position in self.palette_iterator(stage, page) {
1809            if let Some(data) = self.get_tile_render_data(position) {
1810                func(position.stage_position(), data);
1811            }
1812        }
1813    }
1814    /// Loop through the tiles of the given page and find each of the tile colliders on each tile,
1815    /// then pass the collider to the given function along with the collider's UUID and color.
1816    pub fn tile_collider_loop<F>(&self, page: Vector2<i32>, mut func: F)
1817    where
1818        F: FnMut(Vector2<i32>, Uuid, Color, &TileCollider),
1819    {
1820        for layer in self.colliders.iter() {
1821            for position in self.palette_iterator(TilePaletteStage::Tiles, page) {
1822                if let Some(tile_collider) =
1823                    self.get_tile_collider(position.handle().unwrap(), layer.uuid)
1824                {
1825                    if !tile_collider.is_none() {
1826                        func(
1827                            position.stage_position(),
1828                            layer.uuid,
1829                            layer.color,
1830                            tile_collider,
1831                        );
1832                    }
1833                }
1834            }
1835        }
1836    }
1837
1838    /// Some tiles in a tile set are references to tiles elsewhere in the tile set.
1839    /// In particular, the tiles of a transform set page all contain references to other pages,
1840    /// and the page tiles are also references. If this method is given the position of one of these
1841    /// reference tiles, then it returns the handle of the referenced tile.
1842    /// If the given position points to a tile without a redirect, then the tile's handle is returned.
1843    /// If the given position points to a non-existent page or a non-existent tile, then None is returned.
1844    pub fn redirect_handle(&self, position: ResourceTilePosition) -> Option<TileDefinitionHandle> {
1845        match position.stage() {
1846            TilePaletteStage::Tiles => match &self.pages.get(&position.page())?.source {
1847                TileSetPageSource::Transform(tiles) => {
1848                    tiles.get(&position.stage_position()).copied()
1849                }
1850                TileSetPageSource::Animation(tiles) => {
1851                    tiles.get(&position.stage_position()).copied()
1852                }
1853                page => {
1854                    if page.contains_tile_at(position.stage_position()) {
1855                        position.handle()
1856                    } else {
1857                        None
1858                    }
1859                }
1860            },
1861            TilePaletteStage::Pages => self
1862                .pages
1863                .get(&position.stage_position())
1864                .and_then(|p| self.redirect_handle(ResourceTilePosition::from(p.icon))),
1865        }
1866    }
1867    /// If the given handle refers to a transform set page, find the tile on that page and return the handle of wherever
1868    /// the tile comes from originally. Return None if the page is not a transform set or there is no tile at that position.
1869    pub fn get_transform_tile_source(
1870        &self,
1871        handle: TileDefinitionHandle,
1872    ) -> Option<TileDefinitionHandle> {
1873        match &self.pages.get(&handle.page())?.source {
1874            TileSetPageSource::Transform(tiles) => tiles.get(&handle.tile()).copied(),
1875            _ => None,
1876        }
1877    }
1878    /// Returns a clone of the full definition of the tile at the given handle, if possible.
1879    /// Use [TileSet::get_tile_data] if a clone is not needed.
1880    pub fn get_definition(&self, handle: TileDefinitionHandle) -> Option<TileDefinition> {
1881        Some(TileDefinition {
1882            material_bounds: self.get_tile_bounds(handle.into())?,
1883            data: self.get_tile_data(handle.into()).cloned()?,
1884        })
1885    }
1886    /// Return a copy of the definition of the tile at the given handle with the given transformation applied.
1887    pub fn get_transformed_definition(
1888        &self,
1889        trans: OrthoTransformation,
1890        handle: TileDefinitionHandle,
1891    ) -> Option<TileDefinition> {
1892        if let Some(handle) = self.get_transformed_version(trans, handle) {
1893            self.get_definition(handle)
1894        } else {
1895            Some(self.get_definition(handle)?.transformed(trans))
1896        }
1897    }
1898    /// Get the tile definition at the given position.
1899    pub fn get_tile_bounds(&self, position: ResourceTilePosition) -> Option<TileMaterialBounds> {
1900        let handle = self.redirect_handle(position)?;
1901        match &self.pages.get(&handle.page())?.source {
1902            TileSetPageSource::Atlas(mat) => mat.get_tile_bounds(handle.tile()),
1903            TileSetPageSource::Freeform(map) => {
1904                Some(map.get(&handle.tile())?.material_bounds.clone())
1905            }
1906            TileSetPageSource::Transform(_) => None,
1907            TileSetPageSource::Animation(_) => None,
1908        }
1909    }
1910    /// The value of the property with the given UUID for the given tile.
1911    pub fn property_value(
1912        &self,
1913        handle: TileDefinitionHandle,
1914        property_id: Uuid,
1915    ) -> Option<TileSetPropertyValue> {
1916        self.get_tile_data(handle.into()).and_then(|d| {
1917            d.properties.get(&property_id).cloned().or_else(|| {
1918                self.find_property(property_id)
1919                    .map(|p| p.prop_type.default_value())
1920            })
1921        })
1922    }
1923    /// Get the tile definition at the given position.
1924    pub fn get_tile_data(&self, position: ResourceTilePosition) -> Option<&TileData> {
1925        let handle = self.redirect_handle(position)?;
1926        match &self.pages.get(&handle.page())?.source {
1927            TileSetPageSource::Atlas(mat) => mat.get_tile_data(handle.tile()),
1928            TileSetPageSource::Freeform(map) => Some(&map.get(&handle.tile())?.data),
1929            TileSetPageSource::Transform(_) => None,
1930            TileSetPageSource::Animation(_) => None,
1931        }
1932    }
1933    /// Get the tile definition at the given position.
1934    pub fn get_tile_data_mut(&mut self, handle: TileDefinitionHandle) -> Option<&mut TileData> {
1935        match &mut self.pages.get_mut(&handle.page())?.source {
1936            TileSetPageSource::Atlas(mat) => mat.get_tile_data_mut(handle.tile()),
1937            TileSetPageSource::Freeform(map) => Some(&mut map.get_mut(&handle.tile())?.data),
1938            TileSetPageSource::Transform(_) => None,
1939            TileSetPageSource::Animation(_) => None,
1940        }
1941    }
1942
1943    /// The handle of the tile in the animation sequence starting from the given tile handle
1944    /// at the given time, or none if the given handle is not part of any animation sequence.
1945    pub fn get_animated_version(
1946        &self,
1947        time: f32,
1948        handle: TileDefinitionHandle,
1949    ) -> Option<TileDefinitionHandle> {
1950        let (animation, offset) = self.animation_map.get_animation_and_offset(handle)?;
1951        let page = self.get_page(animation.page())?;
1952        let TileSetPageSource::Animation(AnimationTiles { frame_rate, tiles }) = &page.source
1953        else {
1954            return None;
1955        };
1956        let frame_rate = *frame_rate;
1957        let length = animation.length;
1958        let frame_position = (time * frame_rate).rem_euclid(length as f32);
1959        let frame_index = (frame_position.floor() as i32).clamp(0, length - 1);
1960        let frame_index = (frame_index + offset).rem_euclid(length);
1961        let frame = animation.frame(frame_index);
1962        tiles.get(&frame).copied()
1963    }
1964
1965    /// Finds the handle of the tile that represents a transformed version of the tile at the given handle, if such a tile exists.
1966    /// The given tile needs to have a `transform_tile` in its data, that handle needs to point to a transform set page,
1967    /// and that page needs to have a tile in the position corresponding to the desired transform relative to the `transform_tile` position.
1968    /// All 8 possible transforms are grouped together in 4x2 rectangles within each transform set page, and every transformation is possible
1969    /// so long as all 8 cells are filled with tiles. Otherwise, None is returned.
1970    pub fn get_transformed_version(
1971        &self,
1972        transform: OrthoTransformation,
1973        handle: TileDefinitionHandle,
1974    ) -> Option<TileDefinitionHandle> {
1975        if transform.is_identity() {
1976            return Some(handle);
1977        }
1978        let transform_tile = self.transform_map.get(&handle)?;
1979        let page = self.get_page(transform_tile.page())?;
1980        let tiles = match &page.source {
1981            TileSetPageSource::Transform(TransformSetTiles(tiles)) => Some(tiles),
1982            _ => None,
1983        }?;
1984        let cell = TransformSetCell::from_position(transform_tile.tile())
1985            .transformed(transform)
1986            .into_position();
1987        tiles.get(&cell).copied()
1988    }
1989
1990    /// Get the tile definition handles for all of the given coordinates on the given page.
1991    pub fn get_tiles<I: Iterator<Item = Vector2<i32>>>(
1992        &self,
1993        stage: TilePaletteStage,
1994        page: Vector2<i32>,
1995        iter: I,
1996        tiles: &mut Tiles,
1997    ) {
1998        for pos in iter {
1999            if let Some(tile) = self.redirect_handle(ResourceTilePosition::new(stage, page, pos)) {
2000                tiles.insert(pos, tile);
2001            }
2002        }
2003    }
2004    /// The bounding rect of the pages.
2005    pub fn pages_bounds(&self) -> OptionTileRect {
2006        let mut result = OptionTileRect::default();
2007        for pos in self.pages.keys() {
2008            result.push(*pos);
2009        }
2010        result
2011    }
2012    /// The bounding rect of the tiles of the given page.
2013    pub fn tiles_bounds(&self, stage: TilePaletteStage, page: Vector2<i32>) -> OptionTileRect {
2014        match stage {
2015            TilePaletteStage::Tiles => {
2016                let Some(page) = self.pages.get(&page) else {
2017                    return OptionTileRect::default();
2018                };
2019                page.get_bounds()
2020            }
2021            TilePaletteStage::Pages => self.pages_bounds(),
2022        }
2023    }
2024
2025    /// Load a tile set resource from the specific file path.
2026    pub async fn from_file(
2027        path: &Path,
2028        resource_manager: ResourceManager,
2029        io: &dyn ResourceIo,
2030    ) -> Result<Self, TileSetResourceError> {
2031        let bytes = io.load_file(path).await?;
2032        let mut visitor = Visitor::load_from_memory(&bytes)?;
2033        visitor.blackboard.register(Arc::new(resource_manager));
2034        let mut tile_set = TileSet::default();
2035        tile_set.visit("TileSet", &mut visitor)?;
2036        Ok(tile_set)
2037    }
2038
2039    /// Returns true if the tile set is unoccupied at the given position.
2040    pub fn is_free_at(&self, position: ResourceTilePosition) -> bool {
2041        match position.stage() {
2042            TilePaletteStage::Pages => self.get_page(position.stage_position()).is_none(),
2043            TilePaletteStage::Tiles => {
2044                self.get_page(position.page()).is_some()
2045                    && !self.has_tile_at(position.page(), position.stage_position())
2046            }
2047        }
2048    }
2049
2050    /// Tries to find free location at the given position. It uses brute-force searching algorithm
2051    /// and could be slow if called dozens of time per frame or on a large tile set.
2052    pub fn find_free_location(
2053        &self,
2054        stage: TilePaletteStage,
2055        page: Vector2<i32>,
2056        position: Vector2<i32>,
2057    ) -> Vector2<i32> {
2058        let mut visited = FxHashSet::default();
2059        let mut stack = vec![position];
2060        while let Some(pos) = stack.pop() {
2061            if visited.contains(&pos) {
2062                continue;
2063            }
2064            visited.insert(pos);
2065            if self.is_free_at(ResourceTilePosition::new(stage, page, pos)) {
2066                return pos;
2067            } else {
2068                stack.extend_from_slice(&[
2069                    Vector2::new(pos.x + 1, pos.y),
2070                    Vector2::new(pos.x + 1, pos.y + 1),
2071                    Vector2::new(pos.x, pos.y + 1),
2072                    Vector2::new(pos.x - 1, pos.y + 1),
2073                    Vector2::new(pos.x - 1, pos.y),
2074                    Vector2::new(pos.x - 1, pos.y - 1),
2075                    Vector2::new(pos.x, pos.y - 1),
2076                    Vector2::new(pos.x + 1, pos.y - 1),
2077                ]);
2078            }
2079        }
2080        Default::default()
2081    }
2082    /// Take all the values for the property with the given id, remove them from the page, and put them into the given hash map.
2083    /// At the same time, take all the values from the given hash map and put them into the page.
2084    pub fn swap_all_values_for_property(
2085        &mut self,
2086        property_id: Uuid,
2087        values: &mut FxHashMap<TileDefinitionHandle, TileSetPropertyValue>,
2088    ) {
2089        for (page_pos, page) in self.pages.iter_mut() {
2090            page.swap_all_values_for_property(*page_pos, property_id, values);
2091        }
2092    }
2093    /// Take all the colliders for the given collider id, remove them from the tile set, and put them into the given hash map.
2094    /// At the same time, take all the colliders from the given hash map and put them into the page.
2095    pub fn swap_all_values_for_collider(
2096        &mut self,
2097        collider_id: Uuid,
2098        values: &mut FxHashMap<TileDefinitionHandle, TileCollider>,
2099    ) {
2100        for (page_pos, page) in self.pages.iter_mut() {
2101            page.swap_all_values_for_collider(*page_pos, collider_id, values);
2102        }
2103    }
2104}
2105
2106impl ResourceData for TileSet {
2107    fn type_uuid(&self) -> Uuid {
2108        <Self as TypeUuidProvider>::type_uuid()
2109    }
2110
2111    fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
2112        let mut visitor = Visitor::new();
2113        self.visit("TileSet", &mut visitor)?;
2114        visitor.save_ascii_to_file(path)?;
2115        Ok(())
2116    }
2117
2118    fn can_be_saved(&self) -> bool {
2119        true
2120    }
2121
2122    fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
2123        Some(Box::new(self.clone()))
2124    }
2125}
2126
2127/// An alias for `Resource<TileSet>`.
2128pub type TileSetResource = Resource<TileSet>;
2129
2130/// Standard tile set resource loader.
2131pub struct TileSetLoader {
2132    /// Resource manager of the engine.
2133    pub resource_manager: ResourceManager,
2134}
2135
2136impl ResourceLoader for TileSetLoader {
2137    fn extensions(&self) -> &[&str] {
2138        &["tileset"]
2139    }
2140
2141    fn is_native_extension(&self, ext: &str) -> bool {
2142        fyrox_core::cmp_strings_case_insensitive(ext, "tileset")
2143    }
2144
2145    fn data_type_uuid(&self) -> Uuid {
2146        <TileSet as TypeUuidProvider>::type_uuid()
2147    }
2148
2149    fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
2150        let resource_manager = self.resource_manager.clone();
2151        Box::pin(async move {
2152            let mut tile_set = TileSet::from_file(&path, resource_manager, io.as_ref())
2153                .await
2154                .map_err(LoadError::new)?;
2155            tile_set.rebuild_transform_sets();
2156            tile_set.rebuild_animations();
2157            Ok(LoaderPayload::new(tile_set))
2158        })
2159    }
2160}