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