fyrox_impl/scene/tilemap/
mod.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 map is a 2D "image", made out of a small blocks called tiles. Tile maps used in 2D games to
22//! build game worlds quickly and easily. See [`TileMap`] docs for more info and usage examples.
23
24pub mod brush;
25mod data;
26mod effect;
27mod property;
28mod tile_collider;
29mod tile_rect;
30mod tile_source;
31pub mod tileset;
32mod transform;
33mod update;
34
35use brush::*;
36pub use data::*;
37pub use effect::*;
38use fxhash::FxHashSet;
39use fyrox_core::{
40    math::{frustum::Frustum, plane::Plane, ray::Ray},
41    parking_lot::Mutex,
42};
43use fyrox_resource::Resource;
44pub use tile_collider::*;
45pub use tile_rect::*;
46pub use tile_source::*;
47use tileset::*;
48pub use transform::*;
49pub use update::*;
50
51use crate::{
52    asset::{untyped::ResourceKind, ResourceDataRef},
53    core::{
54        algebra::{Matrix4, Vector2, Vector3},
55        color::Color,
56        math::{aabb::AxisAlignedBoundingBox, Matrix4Ext, TriangleDefinition},
57        pool::Handle,
58        reflect::prelude::*,
59        type_traits::prelude::*,
60        variable::InheritableVariable,
61        visitor::prelude::*,
62        ImmutableString,
63    },
64    graph::{constructor::ConstructorProvider, BaseSceneGraph},
65    material::{Material, MaterialResource, STANDARD_2D},
66    renderer::{self, bundle::RenderContext},
67    scene::{
68        base::{Base, BaseBuilder},
69        graph::Graph,
70        mesh::{
71            buffer::{
72                VertexAttributeDataType, VertexAttributeDescriptor, VertexAttributeUsage,
73                VertexTrait,
74            },
75            RenderPath,
76        },
77        node::{Node, NodeTrait, RdcControlFlow},
78        Scene,
79    },
80};
81use bytemuck::{Pod, Zeroable};
82use std::{
83    error::Error,
84    fmt::Display,
85    ops::{Deref, DerefMut},
86    path::PathBuf,
87};
88
89use super::{dim2::rectangle::RectangleVertex, node::constructor::NodeConstructor};
90
91use crate::lazy_static::*;
92
93/// Current implementation version marker.
94pub const VERSION: u8 = 1;
95
96lazy_static! {
97    /// The default material for tiles that have no material set.
98    pub static ref DEFAULT_TILE_MATERIAL: MaterialResource = MaterialResource::new_ok(
99        ResourceKind::External("__DefaultTileMaterial".into()),
100        Material::standard_tile()
101    );
102}
103
104/// Context for rendering tiles in a tile map. It is especially used by
105/// [`TileMapEffect`] objects.
106pub struct TileMapRenderContext<'a, 'b> {
107    /// The underlying render context that tiles will be rendered into.
108    pub context: &'a mut RenderContext<'b>,
109    /// The handle of the TileMap.
110    tile_map_handle: Handle<Node>,
111    /// The global transformation of the TileMap.
112    transform: Matrix4<f32>,
113    /// The visible tile positions.
114    bounds: OptionTileRect,
115    hidden_tiles: &'a mut FxHashSet<Vector2<i32>>,
116    tile_set: OptionTileSet<'a>,
117}
118
119impl TileMapRenderContext<'_, '_> {
120    /// The transformation to apply before rendering
121    pub fn transform(&self) -> &Matrix4<f32> {
122        &self.transform
123    }
124    /// The handle of the [`TileMap`] node
125    pub fn tile_map_handle(&self) -> Handle<Node> {
126        self.tile_map_handle
127    }
128    /// The global position of the TileMap
129    pub fn position(&self) -> Vector3<f32> {
130        self.transform.position()
131    }
132    /// The area of tiles that are touching the frustum
133    pub fn visible_bounds(&self) -> OptionTileRect {
134        self.bounds
135    }
136    /// Set a position to false in order to prevent later effects from rendering
137    /// a tile at this position. All positions are true by default.
138    /// Normally, once a tile has been rendered at a position, the position
139    /// should be set to false to prevent a second tile from being rendered
140    /// at the same position.
141    pub fn set_tile_visible(&mut self, position: Vector2<i32>, is_visible: bool) {
142        if is_visible {
143            let _ = self.hidden_tiles.remove(&position);
144        } else {
145            let _ = self.hidden_tiles.insert(position);
146        }
147    }
148    /// True if tiles should be rendered at that position.
149    /// Normally this should always be checked before rendering a tile
150    /// to prevent the rendering from conflicting with some previous
151    /// effect that has set the position to false.
152    pub fn is_tile_visible(&self, position: Vector2<i32>) -> bool {
153        !self.hidden_tiles.contains(&position)
154    }
155    /// The handle of the tile that should be rendered at the current time in order
156    /// to animate the tile at the given handle.
157    pub fn get_animated_version(&self, handle: TileDefinitionHandle) -> TileDefinitionHandle {
158        self.tile_set
159            .get_animated_version(self.context.elapsed_time, handle)
160            .unwrap_or(handle)
161    }
162    /// Render the tile with the given handle at the given position.
163    /// Normally [`TileMapRenderContext::is_tile_visible`] should be checked before calling this method
164    /// to ensure that tiles are permitted to be rendered at this position,
165    /// and then [`TileMapRenderContext::set_tile_visible`] should be used to set the position to false
166    /// to prevent any future effects from rendering at this position.
167    pub fn draw_tile(&mut self, position: Vector2<i32>, handle: TileDefinitionHandle) {
168        let Some(data) = self.tile_set.get_tile_render_data(handle.into()) else {
169            return;
170        };
171        self.push_tile(position, &data);
172    }
173
174    /// Render the given tile data at the given cell position. This makes it possible to render
175    /// a tile that is not in the tile map's tile set.
176    pub fn push_tile(&mut self, position: Vector2<i32>, data: &TileRenderData) {
177        let color = data.color;
178        if let Some(tile_bounds) = data.material_bounds.as_ref() {
179            let material = &tile_bounds.material;
180            let bounds = &tile_bounds.bounds;
181            self.push_material_tile(position, material, bounds, color);
182        } else {
183            self.push_color_tile(position, color);
184        }
185    }
186
187    fn push_color_tile(&mut self, position: Vector2<i32>, color: Color) {
188        let position = position.cast::<f32>();
189        let vertices = [(0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]
190            .map(|(x, y)| Vector2::new(x, y))
191            .map(|p| make_rect_vertex(&self.transform, position + p, color));
192
193        let triangles = [[0, 1, 2], [2, 3, 0]].map(TriangleDefinition);
194
195        let sort_index = self.context.calculate_sorting_index(self.position());
196
197        self.context.storage.push_triangles(
198            RectangleVertex::layout(),
199            &STANDARD_2D.resource,
200            RenderPath::Forward,
201            sort_index,
202            self.tile_map_handle,
203            &mut move |mut vertex_buffer, mut triangle_buffer| {
204                let start_vertex_index = vertex_buffer.vertex_count();
205
206                vertex_buffer.push_vertices(&vertices).unwrap();
207
208                triangle_buffer
209                    .push_triangles_iter_with_offset(start_vertex_index, triangles.into_iter());
210            },
211        );
212    }
213
214    fn push_material_tile(
215        &mut self,
216        position: Vector2<i32>,
217        material: &MaterialResource,
218        bounds: &TileBounds,
219        color: Color,
220    ) {
221        let position = position.cast::<f32>();
222        let uvs = [
223            bounds.right_top_corner,
224            bounds.left_top_corner,
225            bounds.left_bottom_corner,
226            bounds.right_bottom_corner,
227        ];
228        let vertices = [
229            (1.0, 1.0, uvs[0]),
230            (0.0, 1.0, uvs[1]),
231            (0.0, 0.0, uvs[2]),
232            (1.0, 0.0, uvs[3]),
233        ]
234        .map(|(x, y, uv)| (Vector2::new(x, y), uv))
235        .map(|(p, uv)| make_tile_vertex(&self.transform, position + p, uv, color));
236
237        let triangles = [[0, 1, 2], [2, 3, 0]].map(TriangleDefinition);
238
239        let sort_index = self.context.calculate_sorting_index(self.position());
240
241        self.context.storage.push_triangles(
242            TileVertex::layout(),
243            material,
244            RenderPath::Forward,
245            sort_index,
246            self.tile_map_handle,
247            &mut move |mut vertex_buffer, mut triangle_buffer| {
248                let start_vertex_index = vertex_buffer.vertex_count();
249
250                vertex_buffer.push_vertices(&vertices).unwrap();
251
252                triangle_buffer
253                    .push_triangles_iter_with_offset(start_vertex_index, triangles.into_iter());
254            },
255        );
256    }
257}
258
259fn make_rect_vertex(
260    transform: &Matrix4<f32>,
261    position: Vector2<f32>,
262    color: Color,
263) -> RectangleVertex {
264    RectangleVertex {
265        position: transform
266            .transform_point(&position.to_homogeneous().into())
267            .coords,
268        tex_coord: Vector2::default(),
269        color,
270    }
271}
272
273fn make_tile_vertex(
274    transform: &Matrix4<f32>,
275    position: Vector2<f32>,
276    tex_coord: Vector2<u32>,
277    color: Color,
278) -> TileVertex {
279    TileVertex {
280        position: transform
281            .transform_point(&position.to_homogeneous().into())
282            .coords,
283        tex_coord: tex_coord.cast::<f32>(),
284        color,
285    }
286}
287
288/// A record whether a change has happened since the most recent save.
289#[derive(Default, Debug, Copy, Clone)]
290pub struct ChangeFlag(bool);
291
292impl ChangeFlag {
293    /// True if there are changes.
294    #[inline]
295    pub fn needs_save(&self) -> bool {
296        self.0
297    }
298    /// Reset the flag to indicate that there are no unsaved changes.
299    #[inline]
300    pub fn reset(&mut self) {
301        self.0 = false;
302    }
303    /// Set the flat to indicate that there could be unsaved changes.
304    #[inline]
305    pub fn set(&mut self) {
306        self.0 = true;
307    }
308}
309
310/// A vertex for tiles.
311#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
312#[repr(C)] // OpenGL expects this structure packed as in C
313pub struct TileVertex {
314    /// Position of vertex in local coordinates.
315    pub position: Vector3<f32>,
316    /// Texture coordinates measured in pixels.
317    pub tex_coord: Vector2<f32>,
318    /// Diffuse color.
319    pub color: Color,
320}
321
322impl VertexTrait for TileVertex {
323    fn layout() -> &'static [VertexAttributeDescriptor] {
324        &[
325            VertexAttributeDescriptor {
326                usage: VertexAttributeUsage::Position,
327                data_type: VertexAttributeDataType::F32,
328                size: 3,
329                divisor: 0,
330                shader_location: 0,
331                normalized: false,
332            },
333            VertexAttributeDescriptor {
334                usage: VertexAttributeUsage::TexCoord0,
335                data_type: VertexAttributeDataType::F32,
336                size: 2,
337                divisor: 0,
338                shader_location: 1,
339                normalized: false,
340            },
341            VertexAttributeDescriptor {
342                usage: VertexAttributeUsage::Color,
343                data_type: VertexAttributeDataType::U8,
344                size: 4,
345                divisor: 0,
346                shader_location: 2,
347                normalized: true,
348            },
349        ]
350    }
351}
352
353/// Each brush and tile set has two palette areas: the pages and the tiles within each page.
354/// These two areas are called stages, and each of the two stages needs to be handled separately.
355/// Giving a particular `TilePaletteStage` to a tile map palette will control which kind of
356/// tiles it will display.
357#[derive(Clone, Copy, Default, Debug, Visit, Reflect, PartialEq)]
358pub enum TilePaletteStage {
359    /// The page tile stage. These tiles allow the user to select which page they want to use.
360    #[default]
361    Pages,
362    /// The stage for tiles within a page.
363    Tiles,
364}
365
366/// Tile pages come in these types.
367#[derive(Copy, Clone, Debug, Eq, PartialEq)]
368pub enum PageType {
369    /// A page where tiles get their material from a single shared tile atlas,
370    /// and the UV coordinates of the tile are based on its grid coordinates.
371    Atlas,
372    /// A page where each tile can be assigned any material and UV coordinates.
373    Freeform,
374    /// A page that contains no tile data, but contains handles referencing tiles
375    /// on other pages and specifies how tiles can be flipped and rotated.
376    Transform,
377    /// A page that contains no tile data, but contains handles referencing tiles
378    /// on other pages and specifies how tiles animate over time.
379    /// Animations proceed from left-to-right, with increasing x-coordinate,
380    /// along continuous rows of tiles, until an empty cell is found, and then
381    /// the animation returns to the start of the sequence and repeats.
382    Animation,
383    /// A brush page contains no tile data, but contains handles into a tile set
384    /// where tile data can be found.
385    Brush,
386}
387
388/// The position of a page or a tile within a tile resource.
389/// Despite the difference between pages and tiles, they have enough similarities
390/// that it is sometimes useful to view them abstractly as the same.
391/// Both pages and tiles have a `Vecto2<i32>` position.
392/// Both pages and tiles have a TileDefinitionHandle and are rendered using
393/// [`TileRenderData`]. For pages this is due to having an icon to allow the user to select the page.
394/// Both pages and tiles can be selected by the user, moved, and deleted.
395#[derive(Clone, Copy, Debug, PartialEq)]
396pub enum ResourceTilePosition {
397    /// This position refers to some page, and so it lacks tile coordinates.
398    Page(Vector2<i32>),
399    /// This position refers to some tile, and so it has page coordinates and
400    /// the coordinates of the tile within the page.
401    Tile(Vector2<i32>, Vector2<i32>),
402}
403
404impl From<TileDefinitionHandle> for ResourceTilePosition {
405    fn from(value: TileDefinitionHandle) -> Self {
406        Self::Tile(value.page(), value.tile())
407    }
408}
409
410impl ResourceTilePosition {
411    /// Construct a position from the given stage, page, and tile.
412    /// If the stage is [`TilePaletteStage::Pages`] then this position is refering to some page
413    /// as if it were a tile, and therefore the `page` argument is ignored and the `tile` argument
414    /// is taken as the page's position.
415    pub fn new(stage: TilePaletteStage, page: Vector2<i32>, tile: Vector2<i32>) -> Self {
416        match stage {
417            TilePaletteStage::Pages => Self::Page(tile),
418            TilePaletteStage::Tiles => Self::Tile(page, tile),
419        }
420    }
421    /// This position refers to some page.
422    pub fn is_page(&self) -> bool {
423        matches!(self, Self::Page(_))
424    }
425    /// This position refers to a tile within a page.
426    pub fn is_tile(&self) -> bool {
427        matches!(self, Self::Tile(_, _))
428    }
429    /// The stage that contains this position.
430    pub fn stage(&self) -> TilePaletteStage {
431        match self {
432            Self::Page(_) => TilePaletteStage::Pages,
433            Self::Tile(_, _) => TilePaletteStage::Tiles,
434        }
435    }
436    /// The position within the stage. For a page position, this is the page's coordinates.
437    /// For a tile position, this is the tile's coordinates.
438    pub fn stage_position(&self) -> Vector2<i32> {
439        match self {
440            Self::Page(p) => *p,
441            Self::Tile(_, p) => *p,
442        }
443    }
444    /// The page coordinates of the position. For a page position, this is
445    pub fn page(&self) -> Vector2<i32> {
446        match self {
447            Self::Page(p) => *p,
448            Self::Tile(p, _) => *p,
449        }
450    }
451    /// The handle associated with this position, if this is a tile position.
452    pub fn handle(&self) -> Option<TileDefinitionHandle> {
453        if let Self::Tile(p, t) = self {
454            TileDefinitionHandle::try_new(*p, *t)
455        } else {
456            None
457        }
458    }
459}
460
461/// Tile is a base block of a tile map. It has a position and a handle of tile definition, stored
462/// in the respective tile set.
463#[derive(Clone, Reflect, Default, Debug, PartialEq, Visit, ComponentProvider, TypeUuidProvider)]
464#[type_uuid(id = "e429ca1b-a311-46c3-b580-d5a2f49db7e2")]
465pub struct Tile {
466    /// Position of the tile (in grid coordinates).
467    pub position: Vector2<i32>,
468    /// A handle of the tile definition.
469    pub definition_handle: TileDefinitionHandle,
470}
471
472/// Adapt an iterator over positions into an iterator over `(Vector2<i32>, TileHandleDefinition)`.
473#[derive(Debug, Clone)]
474pub struct TileIter<I> {
475    source: TileBook,
476    stage: TilePaletteStage,
477    page: Vector2<i32>,
478    positions: I,
479}
480
481impl<I: Iterator<Item = Vector2<i32>>> Iterator for TileIter<I> {
482    type Item = (Vector2<i32>, TileDefinitionHandle);
483
484    fn next(&mut self) -> Option<Self::Item> {
485        self.positions.find_map(|p| {
486            let h = self
487                .source
488                .get_tile_handle(ResourceTilePosition::new(self.stage, self.page, p))?;
489            Some((p, h))
490        })
491    }
492}
493
494#[derive(Debug, Default, Clone, PartialEq, Visit, Reflect)]
495/// Abstract source of tiles, which can either be a tile set or a brush.
496/// It is called a "book" because each of these tile resources contains
497/// pages of tiles.
498pub enum TileBook {
499    /// A tile resource containing no tiles.
500    #[default]
501    Empty,
502    /// Getting tiles from a tile set
503    TileSet(TileSetResource),
504    /// Getting tiles from a brush
505    Brush(TileMapBrushResource),
506}
507
508impl TileBook {
509    /// The TileDefinitionHandle of the icon that represents the page at the given position.
510    #[inline]
511    pub fn page_icon(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
512        match self {
513            TileBook::Empty => None,
514            TileBook::TileSet(r) => r.state().data()?.page_icon(position),
515            TileBook::Brush(r) => r.state().data()?.page_icon(position),
516        }
517    }
518    /// Returns true if this resource is a tile set.
519    #[inline]
520    pub fn is_tile_set(&self) -> bool {
521        matches!(self, TileBook::TileSet(_))
522    }
523    /// Returns true if this resource is a brush.
524    #[inline]
525    pub fn is_brush(&self) -> bool {
526        matches!(self, TileBook::Brush(_))
527    }
528    /// Returns true if this contains no resource.
529    #[inline]
530    pub fn is_empty(&self) -> bool {
531        matches!(self, TileBook::Empty)
532    }
533    /// Return the path of the resource as a String.
534    pub fn name(&self) -> String {
535        self.path()
536            .map(|x| x.to_string_lossy().into_owned())
537            .unwrap_or_else(|| "Error".into())
538    }
539    /// Return the path of the resource.
540    pub fn path(&self) -> Option<PathBuf> {
541        match self {
542            TileBook::Empty => None,
543            TileBook::TileSet(r) => r.kind().into_path(),
544            TileBook::Brush(r) => r.kind().into_path(),
545        }
546    }
547    /// True if the resource is external and its `change_count` is not zero.
548    pub fn needs_save(&self) -> bool {
549        match self {
550            TileBook::Empty => false,
551            TileBook::TileSet(r) => {
552                r.header().kind.is_external() && r.data_ref().change_count.needs_save()
553            }
554            TileBook::Brush(r) => {
555                r.header().kind.is_external() && r.data_ref().change_count.needs_save()
556            }
557        }
558    }
559    /// Attempt to save the resource to its file, if it has one and if `change_count` not zero.
560    /// Otherwise do nothing and return Ok to indicate success.
561    pub fn save(&self) -> Result<(), Box<dyn Error>> {
562        match self {
563            TileBook::Empty => Ok(()),
564            TileBook::TileSet(r) => {
565                if r.header().kind.is_external() && r.data_ref().change_count.needs_save() {
566                    let result = r.save_back();
567                    if result.is_ok() {
568                        r.data_ref().change_count.reset();
569                    }
570                    result
571                } else {
572                    Ok(())
573                }
574            }
575            TileBook::Brush(r) => {
576                if r.header().kind.is_external() && r.data_ref().change_count.needs_save() {
577                    let result = r.save_back();
578                    if result.is_ok() {
579                        r.data_ref().change_count.reset();
580                    }
581                    result
582                } else {
583                    Ok(())
584                }
585            }
586        }
587    }
588    /// A reference to the TileSetResource, if this is a TileSetResource.
589    pub fn tile_set_ref(&self) -> Option<&TileSetResource> {
590        match self {
591            TileBook::TileSet(r) => Some(r),
592            _ => None,
593        }
594    }
595    /// Returns the tile set associated with this resource.
596    /// If the resource is a tile set, the return that tile set.
597    /// If the resource is a brush, then return the tile set used by that brush.
598    pub fn get_tile_set(&self) -> Option<TileSetResource> {
599        match self {
600            TileBook::Empty => None,
601            TileBook::TileSet(r) => Some(r.clone()),
602            TileBook::Brush(r) => r.state().data()?.tile_set.clone(),
603        }
604    }
605    /// Build a list of the positions of all tiles on the given page.
606    pub fn get_all_tile_positions(&self, page: Vector2<i32>) -> Vec<Vector2<i32>> {
607        match self {
608            TileBook::Empty => Vec::new(),
609            TileBook::TileSet(r) => r
610                .state()
611                .data()
612                .map(|r| r.keys_on_page(page))
613                .unwrap_or_default(),
614            TileBook::Brush(r) => r
615                .state()
616                .data()
617                .and_then(|r| {
618                    r.pages
619                        .get(&page)
620                        .map(|p| p.tiles.keys().copied().collect())
621                })
622                .unwrap_or_default(),
623        }
624    }
625    /// Build a list of the posiitons of all pages.
626    pub fn get_all_page_positions(&self) -> Vec<Vector2<i32>> {
627        match self {
628            TileBook::Empty => Vec::new(),
629            TileBook::TileSet(r) => r.state().data().map(|r| r.page_keys()).unwrap_or_default(),
630            TileBook::Brush(r) => r
631                .state()
632                .data()
633                .map(|r| r.pages.keys().copied().collect())
634                .unwrap_or_default(),
635        }
636    }
637    /// True if there is a page at the given position.
638    pub fn has_page_at(&self, position: Vector2<i32>) -> bool {
639        match self {
640            TileBook::Empty => false,
641            TileBook::TileSet(r) => r
642                .state()
643                .data()
644                .map(|r| r.pages.contains_key(&position))
645                .unwrap_or(false),
646            TileBook::Brush(r) => r
647                .state()
648                .data()
649                .map(|r| r.pages.contains_key(&position))
650                .unwrap_or(false),
651        }
652    }
653    /// The type of the page at the given position, if there is one.
654    pub fn page_type(&self, position: Vector2<i32>) -> Option<PageType> {
655        match self {
656            TileBook::Empty => None,
657            TileBook::TileSet(r) => r.state().data()?.get_page(position).map(|p| p.page_type()),
658            TileBook::Brush(r) => {
659                if r.state().data()?.has_page_at(position) {
660                    Some(PageType::Brush)
661                } else {
662                    None
663                }
664            }
665        }
666    }
667    /// True if there is a atlas page at the given coordinates.
668    pub fn is_atlas_page(&self, position: Vector2<i32>) -> bool {
669        self.page_type(position) == Some(PageType::Atlas)
670    }
671    /// True if there is a free tile page at the given coordinates.
672    pub fn is_free_page(&self, position: Vector2<i32>) -> bool {
673        self.page_type(position) == Some(PageType::Freeform)
674    }
675    /// True if there is a transform page at the given coordinates.
676    pub fn is_transform_page(&self, position: Vector2<i32>) -> bool {
677        self.page_type(position) == Some(PageType::Transform)
678    }
679    /// True if there is a transform page at the given coordinates.
680    pub fn is_animation_page(&self, position: Vector2<i32>) -> bool {
681        self.page_type(position) == Some(PageType::Animation)
682    }
683    /// True if there is a brush page at the given coordinates.
684    pub fn is_brush_page(&self, position: Vector2<i32>) -> bool {
685        self.page_type(position) == Some(PageType::Brush)
686    }
687    /// Return true if there is a tile at the given position on the page at the given position.
688    pub fn has_tile_at(&self, page: Vector2<i32>, tile: Vector2<i32>) -> bool {
689        match self {
690            TileBook::Empty => false,
691            TileBook::TileSet(r) => r
692                .state()
693                .data()
694                .map(|r| r.has_tile_at(page, tile))
695                .unwrap_or(false),
696            TileBook::Brush(r) => r
697                .state()
698                .data()
699                .map(|r| r.has_tile_at(page, tile))
700                .unwrap_or(false),
701        }
702    }
703    /// Returns the TileDefinitionHandle that points to the data in the tile set that represents this tile.
704    /// Even if this resource is actually a brush, the handle returned still refers to some page and position
705    /// in the brush's tile set.
706    pub fn get_tile_handle(&self, position: ResourceTilePosition) -> Option<TileDefinitionHandle> {
707        match self {
708            TileBook::Empty => None,
709            TileBook::TileSet(r) => r.state().data()?.redirect_handle(position),
710            TileBook::Brush(r) => r.state().data()?.redirect_handle(position),
711        }
712    }
713    /// Returns an iterator over `(Vector2<i32>, TileDefinitionHandle)` where the first
714    /// member of the pair is the position of the tile on the page as provided by `positions`
715    /// and the second member is the handle that would be returned from [`get_tile_handle`](Self::get_tile_handle).
716    pub fn get_tile_iter<I: Iterator<Item = Vector2<i32>>>(
717        &self,
718        stage: TilePaletteStage,
719        page: Vector2<i32>,
720        positions: I,
721    ) -> TileIter<I> {
722        TileIter {
723            source: self.clone(),
724            stage,
725            page,
726            positions,
727        }
728    }
729    /// Construct a Tiles object holding the tile definition handles for the tiles
730    /// at the given positions on the given page.
731    pub fn get_tiles<I: Iterator<Item = Vector2<i32>>>(
732        &self,
733        stage: TilePaletteStage,
734        page: Vector2<i32>,
735        iter: I,
736        tiles: &mut Tiles,
737    ) {
738        match self {
739            TileBook::Empty => (),
740            TileBook::TileSet(res) => {
741                if let Some(tile_set) = res.state().data() {
742                    tile_set.get_tiles(stage, page, iter, tiles);
743                }
744            }
745            TileBook::Brush(res) => {
746                if let Some(brush) = res.state().data() {
747                    brush.get_tiles(stage, page, iter, tiles);
748                }
749            }
750        }
751    }
752
753    /// Returns true if the resource is a brush that has no tile set.
754    pub fn is_missing_tile_set(&self) -> bool {
755        match self {
756            TileBook::Empty => false,
757            TileBook::TileSet(_) => false,
758            TileBook::Brush(resource) => resource
759                .state()
760                .data()
761                .map(|b| b.is_missing_tile_set())
762                .unwrap_or(false),
763        }
764    }
765
766    /// Return the `TileRenderData` needed to render the tile at the given position on the given page.
767    /// If there is no tile at that position or the tile set is missing or not loaded, then None is returned.
768    /// If there is a tile and a tile set, but the handle of the tile does not exist in the tile set,
769    /// then the rendering data for an error tile is returned using `TileRenderData::missing_tile()`.
770    ///
771    /// Beware that this method is *slow.* Like most methods in `TileBook`, this method involves
772    /// locking a resource, so none of them should be called many times per frame, as one might be
773    /// tempted to do with this method. Do *not* render a `TileBook` by repeatedly calling this
774    /// method. Use [`tile_render_loop`](Self::tile_render_loop) instead.
775    pub fn get_tile_render_data(&self, position: ResourceTilePosition) -> Option<TileRenderData> {
776        match self {
777            TileBook::Empty => None,
778            TileBook::TileSet(resource) => resource.state().data()?.get_tile_render_data(position),
779            TileBook::Brush(resource) => resource.state().data()?.get_tile_render_data(position),
780        }
781    }
782
783    /// Repeatedly call the given function with each tile for the given stage and page.
784    /// The function is given the position of the tile within the palette and the
785    /// data for rendering the tile.
786    pub fn tile_render_loop<F>(&self, stage: TilePaletteStage, page: Vector2<i32>, func: F)
787    where
788        F: FnMut(Vector2<i32>, TileRenderData),
789    {
790        match self {
791            TileBook::Empty => (),
792            TileBook::TileSet(res) => {
793                if let Some(data) = res.state().data() {
794                    data.palette_render_loop(stage, page, func)
795                }
796            }
797            TileBook::Brush(res) => {
798                if let Some(data) = res.state().data() {
799                    data.palette_render_loop(stage, page, func)
800                }
801            }
802        };
803    }
804    /// Repeatedly call the given function with each collider for each tile on the given page.
805    /// The function is given the position of the tile
806    pub fn tile_collider_loop<F>(&self, page: Vector2<i32>, func: F)
807    where
808        F: FnMut(Vector2<i32>, Uuid, Color, &TileCollider),
809    {
810        match self {
811            TileBook::Empty => (),
812            TileBook::TileSet(res) => {
813                if let Some(data) = res.state().data() {
814                    data.tile_collider_loop(page, func)
815                }
816            }
817            TileBook::Brush(_) => (),
818        };
819    }
820    /// Returns the rectangle within a material that a tile should show
821    /// at the given stage and handle.
822    pub fn get_tile_bounds(&self, position: ResourceTilePosition) -> Option<TileMaterialBounds> {
823        match self {
824            TileBook::Empty => None,
825            TileBook::TileSet(res) => res
826                .state()
827                .data()
828                .map(|d| d.get_tile_bounds(position))
829                .unwrap_or_default(),
830            TileBook::Brush(res) => res
831                .state()
832                .data()
833                .map(|d| d.get_tile_bounds(position))
834                .unwrap_or_default(),
835        }
836    }
837    /// The bounds of the tiles on the given page.
838    pub fn tiles_bounds(&self, stage: TilePaletteStage, page: Vector2<i32>) -> OptionTileRect {
839        match self {
840            TileBook::Empty => OptionTileRect::default(),
841            TileBook::TileSet(res) => res.data_ref().tiles_bounds(stage, page),
842            TileBook::Brush(res) => res.data_ref().tiles_bounds(stage, page),
843        }
844    }
845}
846
847/// The specification for how to render a tile.
848#[derive(Clone, Default, Debug)]
849pub struct TileRenderData {
850    /// The material to use to render this tile.
851    pub material_bounds: Option<TileMaterialBounds>,
852    /// The color to use to render the tile
853    pub color: Color,
854}
855
856impl TileRenderData {
857    /// Returns TileRenderData to represent an error due to render data being unavailable.
858    pub fn missing_data() -> TileRenderData {
859        Self {
860            material_bounds: None,
861            color: Color::HOT_PINK,
862        }
863    }
864}
865
866impl OrthoTransform for TileRenderData {
867    fn x_flipped(mut self) -> Self {
868        self.material_bounds = self.material_bounds.map(|b| b.x_flipped());
869        self
870    }
871
872    fn rotated(mut self, amount: i8) -> Self {
873        self.material_bounds = self.material_bounds.map(|b| b.rotated(amount));
874        self
875    }
876}
877
878/// Tile map is a 2D "image", made out of a small blocks called tiles. Tile maps used in 2D games to
879/// build game worlds quickly and easily. Each tile is represented by a [`TileDefinitionHandle`] which
880/// contains the position of a page and the position of a tile within that page.
881///
882/// When rendering the `TileMap`, the rendering data is fetched from the tile map's tile set resource,
883/// which contains all the pages that may be referenced by the tile map's handles.
884///
885/// Optional [`TileMapEffect`] objects may be included in the `TileMap` to change how it renders.
886#[derive(Reflect, Debug, ComponentProvider, TypeUuidProvider)]
887#[type_uuid(id = "aa9a3385-a4af-4faf-a69a-8d3af1a3aa67")]
888pub struct TileMap {
889    base: Base,
890    /// The source of rendering data for tiles in this tile map.
891    tile_set: InheritableVariable<Option<TileSetResource>>,
892    /// Tile container of the tile map.
893    #[reflect(hidden)]
894    pub tiles: InheritableVariable<Option<TileMapDataResource>>,
895    tile_scale: InheritableVariable<Vector2<f32>>,
896    active_brush: InheritableVariable<Option<TileMapBrushResource>>,
897    /// Temporary space to store which tiles are invisible during `collect_render_data`.
898    /// This is part of how [`TileMapEffect`] can prevent a tile from being rendered.
899    #[reflect(hidden)]
900    hidden_tiles: Mutex<FxHashSet<Vector2<i32>>>,
901    /// Special rendering effects that may change how the tile map renders.
902    /// These effects are processed in order before the tile map performs the
903    /// normal rendering of tiles, and they can prevent some times from being
904    /// rendered and render other tiles in place of what would normally be
905    /// rendered.
906    #[reflect(hidden)]
907    pub before_effects: Vec<TileMapEffectRef>,
908    /// Special rendering effects that may change how the tile map renders.
909    /// These effects are processed in order after the tile map performs the
910    /// normal rendering of tiles.
911    #[reflect(hidden)]
912    pub after_effects: Vec<TileMapEffectRef>,
913}
914
915impl Visit for TileMap {
916    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
917        let mut region = visitor.enter_region(name)?;
918        let mut version = if region.is_reading() { 0 } else { VERSION };
919        let _ = version.visit("Version", &mut region);
920        self.base.visit("Base", &mut region)?;
921        self.tile_set.visit("TileSet", &mut region)?;
922        self.tile_scale.visit("TileScale", &mut region)?;
923        self.active_brush.visit("ActiveBrush", &mut region)?;
924        match version {
925            0 => {
926                let mut tiles = InheritableVariable::new_non_modified(Tiles::default());
927                let result = tiles.visit("Tiles", &mut region);
928                result?;
929                let mut data = TileMapData::default();
930                for (p, h) in tiles.iter() {
931                    data.set(*p, *h);
932                }
933                self.tiles = Some(Resource::new_ok(ResourceKind::Embedded, data)).into();
934            }
935            VERSION => {
936                self.tiles.visit("Tiles", &mut region)?;
937            }
938            _ => return Err(VisitError::User("Unknown version".into())),
939        }
940        Ok(())
941    }
942}
943
944/// A reference to the tile data of a some tile in a tile set.
945pub struct TileMapDataRef<'a> {
946    tile_set: ResourceDataRef<'a, TileSet>,
947    handle: TileDefinitionHandle,
948}
949
950impl Deref for TileMapDataRef<'_> {
951    type Target = TileData;
952
953    fn deref(&self) -> &Self::Target {
954        self.tile_set.tile_data(self.handle).unwrap()
955    }
956}
957
958/// An error in finding a property for a tile.
959#[derive(Debug)]
960pub enum TilePropertyError {
961    /// The tile map has no tile set, so not tile data is available.
962    MissingTileSet,
963    /// The tile map has a tile set, but it is not yet loaded.
964    TileSetNotLoaded,
965    /// There is no property with the given name in the tile set.
966    UnrecognizedName(ImmutableString),
967    /// There is no property with the given UUID in the tile set.
968    UnrecognizedUuid(Uuid),
969    /// The property has the wrong type.
970    WrongType(&'static str),
971}
972
973impl Display for TilePropertyError {
974    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
975        match self {
976            TilePropertyError::MissingTileSet => write!(f, "The tile map has no tile set."),
977            TilePropertyError::TileSetNotLoaded => {
978                write!(f, "The tile map's tile set is not loaded.")
979            }
980            TilePropertyError::UnrecognizedName(name) => {
981                write!(f, "There is no property with this name: {name}")
982            }
983            TilePropertyError::UnrecognizedUuid(uuid) => {
984                write!(f, "There is no property with this UUID: {uuid}")
985            }
986            TilePropertyError::WrongType(message) => write!(f, "Property type error: {message}"),
987        }
988    }
989}
990
991impl Error for TilePropertyError {}
992
993impl TileMap {
994    /// The handle that is stored in the tile map at the given position to refer to some tile in the tile set.
995    pub fn tile_handle(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
996        let tiles = self.tiles.as_ref()?.data_ref();
997        tiles.as_loaded_ref()?.get(position)
998    }
999    /// The tile data for the tile at the given position, if that position has a tile and this tile map
1000    /// has a tile set that contains data for the tile's handle.
1001    pub fn tile_data(&self, position: Vector2<i32>) -> Option<TileMapDataRef> {
1002        let handle = self.tile_handle(position)?;
1003        let tile_set = self.tile_set.as_ref()?.data_ref();
1004        if tile_set.as_loaded_ref()?.tile_data(handle).is_some() {
1005            Some(TileMapDataRef { tile_set, handle })
1006        } else {
1007            None
1008        }
1009    }
1010    /// The property value for the property of the given name for the tile at the given position in this tile map.
1011    /// This requires that the tile map has a loaded tile set and the tile set contains a property with the given name.
1012    /// Otherwise an error is returned to indicate which of these conditions failed.
1013    /// If the only problem is that there is no tile at the given position, then the default value for the property's value type
1014    /// is returned.
1015    pub fn tile_property_value<T>(
1016        &self,
1017        position: Vector2<i32>,
1018        property_id: Uuid,
1019    ) -> Result<T, TilePropertyError>
1020    where
1021        T: TryFrom<TileSetPropertyValue, Error = TilePropertyError> + Default,
1022    {
1023        let Some(handle) = self.tile_handle(position) else {
1024            return Ok(T::default());
1025        };
1026        let tile_set = self
1027            .tile_set
1028            .as_ref()
1029            .ok_or(TilePropertyError::MissingTileSet)?
1030            .data_ref();
1031        let tile_set = tile_set
1032            .as_loaded_ref()
1033            .ok_or(TilePropertyError::TileSetNotLoaded)?;
1034        tile_set
1035            .property_value(handle, property_id)
1036            .map(T::try_from)
1037            .unwrap_or_else(|| Ok(T::default()))
1038    }
1039    /// The property value for the property of the given name for the tile at the given position in this tile map.
1040    /// This requires that the tile map has a loaded tile set and the tile set contains a property with the given name.
1041    /// Otherwise an error is returned to indicate which of these conditions failed.
1042    /// If the only problem is that there is no tile at the given position, then the default value for the property's value type
1043    /// is returned.
1044    pub fn tile_property_value_by_name(
1045        &self,
1046        position: Vector2<i32>,
1047        property_name: &ImmutableString,
1048    ) -> Result<TileSetPropertyValue, TilePropertyError> {
1049        let tile_set = self
1050            .tile_set
1051            .as_ref()
1052            .ok_or(TilePropertyError::MissingTileSet)?
1053            .data_ref();
1054        let tile_set = tile_set
1055            .as_loaded_ref()
1056            .ok_or(TilePropertyError::TileSetNotLoaded)?;
1057        let property = tile_set
1058            .find_property_by_name(property_name)
1059            .ok_or_else(|| TilePropertyError::UnrecognizedName(property_name.clone()))?;
1060        let Some(handle) = self.tile_handle(position) else {
1061            return Ok(property.prop_type.default_value());
1062        };
1063        Ok(tile_set
1064            .property_value(handle, property.uuid)
1065            .unwrap_or_else(|| property.prop_type.default_value()))
1066    }
1067    /// The property value for the property of the given UUID for the tile at the given position in this tile map.
1068    /// This requires that the tile map has a loaded tile set and the tile set contains a property with the given UUID.
1069    /// Otherwise an error is returned to indicate which of these conditions failed.
1070    /// If the only problem is that there is no tile at the given position, then the default value for the property's value type
1071    /// is returned.
1072    pub fn tile_property_value_by_uuid_untyped(
1073        &self,
1074        position: Vector2<i32>,
1075        property_id: Uuid,
1076    ) -> Result<TileSetPropertyValue, TilePropertyError> {
1077        let tile_set = self
1078            .tile_set
1079            .as_ref()
1080            .ok_or(TilePropertyError::MissingTileSet)?
1081            .data_ref();
1082        let tile_set = tile_set
1083            .as_loaded_ref()
1084            .ok_or(TilePropertyError::TileSetNotLoaded)?;
1085        let value = if let Some(handle) = self.tile_handle(position) {
1086            tile_set
1087                .tile_data(handle)
1088                .and_then(|d| d.properties.get(&property_id))
1089                .cloned()
1090        } else {
1091            None
1092        };
1093        if let Some(value) = value {
1094            Ok(value)
1095        } else {
1096            let property = tile_set
1097                .find_property(property_id)
1098                .ok_or(TilePropertyError::UnrecognizedUuid(property_id))?;
1099            Ok(property.prop_type.default_value())
1100        }
1101    }
1102    /// The global transform of the tile map with initial x-axis flip applied, so the positive x-axis points left instead of right.
1103    pub fn tile_map_transform(&self) -> Matrix4<f32> {
1104        self.global_transform()
1105            .prepend_nonuniform_scaling(&Vector3::new(-1.0, 1.0, 1.0))
1106    }
1107    /// Returns a reference to the current tile set (if any).
1108    #[inline]
1109    pub fn tile_set(&self) -> Option<&TileSetResource> {
1110        self.tile_set.as_ref()
1111    }
1112
1113    /// Sets new tile set.
1114    #[inline]
1115    pub fn set_tile_set(&mut self, tile_set: Option<TileSetResource>) {
1116        self.tile_set.set_value_and_mark_modified(tile_set);
1117    }
1118
1119    /// Returns a reference to the tile container.
1120    #[inline]
1121    pub fn tiles(&self) -> Option<&TileMapDataResource> {
1122        self.tiles.as_ref()
1123    }
1124
1125    /// Sets new tiles.
1126    #[inline]
1127    pub fn set_tiles(&mut self, tiles: TileMapDataResource) {
1128        self.tiles.set_value_and_mark_modified(Some(tiles));
1129    }
1130
1131    /// Returns current tile scaling.
1132    #[inline]
1133    pub fn tile_scale(&self) -> Vector2<f32> {
1134        *self.tile_scale
1135    }
1136
1137    /// Sets new tile scaling, which defines tile size.
1138    #[inline]
1139    pub fn set_tile_scale(&mut self, tile_scale: Vector2<f32>) {
1140        self.tile_scale.set_value_and_mark_modified(tile_scale);
1141    }
1142
1143    /// Inserts a tile in the tile map. Returns previous tile, located at the same position as
1144    /// the new one (if any).
1145    #[inline]
1146    pub fn insert_tile(
1147        &mut self,
1148        position: Vector2<i32>,
1149        tile: TileDefinitionHandle,
1150    ) -> Option<TileDefinitionHandle> {
1151        self.tiles
1152            .as_ref()?
1153            .data_ref()
1154            .as_loaded_mut()?
1155            .replace(position, Some(tile))
1156    }
1157
1158    /// Removes a tile from the tile map.
1159    #[inline]
1160    pub fn remove_tile(&mut self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
1161        self.tiles
1162            .as_ref()?
1163            .data_ref()
1164            .as_loaded_mut()?
1165            .replace(position, None)
1166    }
1167
1168    /// Returns active brush of the tile map.
1169    #[inline]
1170    pub fn active_brush(&self) -> Option<&TileMapBrushResource> {
1171        self.active_brush.as_ref()
1172    }
1173
1174    /// Sets new active brush of the tile map.
1175    #[inline]
1176    pub fn set_active_brush(&mut self, brush: Option<TileMapBrushResource>) {
1177        self.active_brush.set_value_and_mark_modified(brush);
1178    }
1179
1180    /// Calculates bounding rectangle in grid coordinates.
1181    #[inline]
1182    pub fn bounding_rect(&self) -> OptionTileRect {
1183        let Some(tiles) = self.tiles.as_ref().map(|r| r.data_ref()) else {
1184            return OptionTileRect::default();
1185        };
1186        let Some(tiles) = tiles.as_loaded_ref() else {
1187            return OptionTileRect::default();
1188        };
1189        tiles.bounding_rect()
1190    }
1191
1192    /// Calculates grid-space position (tile coordinates) from world-space. Could be used to find
1193    /// tile coordinates from arbitrary point in world space. It is especially useful, if the tile
1194    /// map is rotated or shifted.
1195    #[inline]
1196    pub fn world_to_grid(&self, world_position: Vector3<f32>) -> Vector2<i32> {
1197        let inv_global_transform = self.tile_map_transform().try_inverse().unwrap_or_default();
1198        let local_space_position = inv_global_transform.transform_point(&world_position.into());
1199        Vector2::new(
1200            local_space_position.x.floor() as i32,
1201            local_space_position.y.floor() as i32,
1202        )
1203    }
1204
1205    /// Calculates world-space position from grid-space position (tile coordinates).
1206    #[inline]
1207    pub fn grid_to_world(&self, grid_position: Vector2<i32>) -> Vector3<f32> {
1208        let v3 = grid_position.cast::<f32>().to_homogeneous();
1209        self.tile_map_transform().transform_point(&v3.into()).coords
1210    }
1211
1212    fn cells_touching_frustum(&self, frustum: &Frustum) -> OptionTileRect {
1213        let global_transform = self.global_transform();
1214
1215        fn make_ray(a: Vector3<f32>, b: Vector3<f32>) -> Ray {
1216            Ray {
1217                origin: a,
1218                dir: b - a,
1219            }
1220        }
1221
1222        let left_top_ray = make_ray(
1223            frustum.left_top_front_corner(),
1224            frustum.left_top_back_corner(),
1225        );
1226        let right_top_ray = make_ray(
1227            frustum.right_top_front_corner(),
1228            frustum.right_top_back_corner(),
1229        );
1230        let left_bottom_ray = make_ray(
1231            frustum.left_bottom_front_corner(),
1232            frustum.left_bottom_back_corner(),
1233        );
1234        let right_bottom_ray = make_ray(
1235            frustum.right_bottom_front_corner(),
1236            frustum.right_bottom_back_corner(),
1237        );
1238
1239        let plane =
1240            Plane::from_normal_and_point(&global_transform.look(), &global_transform.position())
1241                .unwrap_or_default();
1242
1243        let Some(left_top) = left_top_ray.plane_intersection_point(&plane) else {
1244            return None.into();
1245        };
1246        let Some(right_top) = right_top_ray.plane_intersection_point(&plane) else {
1247            return None.into();
1248        };
1249        let Some(left_bottom) = left_bottom_ray.plane_intersection_point(&plane) else {
1250            return None.into();
1251        };
1252        let Some(right_bottom) = right_bottom_ray.plane_intersection_point(&plane) else {
1253            return None.into();
1254        };
1255        let mut bounds = OptionTileRect::default();
1256        for corner in [left_top, right_top, left_bottom, right_bottom] {
1257            bounds.push(self.world_to_grid(corner))
1258        }
1259        bounds
1260    }
1261}
1262
1263impl Default for TileMap {
1264    fn default() -> Self {
1265        Self {
1266            base: Default::default(),
1267            tile_set: Default::default(),
1268            tiles: Default::default(),
1269            tile_scale: Vector2::repeat(1.0).into(),
1270            active_brush: Default::default(),
1271            hidden_tiles: Mutex::default(),
1272            before_effects: Vec::default(),
1273            after_effects: Vec::default(),
1274        }
1275    }
1276}
1277
1278impl Clone for TileMap {
1279    fn clone(&self) -> Self {
1280        Self {
1281            base: self.base.clone(),
1282            tile_set: self.tile_set.clone(),
1283            tiles: self.tiles.clone(),
1284            tile_scale: self.tile_scale.clone(),
1285            active_brush: self.active_brush.clone(),
1286            hidden_tiles: Mutex::default(),
1287            before_effects: self.before_effects.clone(),
1288            after_effects: self.after_effects.clone(),
1289        }
1290    }
1291}
1292
1293impl Deref for TileMap {
1294    type Target = Base;
1295
1296    fn deref(&self) -> &Self::Target {
1297        &self.base
1298    }
1299}
1300
1301impl DerefMut for TileMap {
1302    fn deref_mut(&mut self) -> &mut Self::Target {
1303        &mut self.base
1304    }
1305}
1306
1307impl ConstructorProvider<Node, Graph> for TileMap {
1308    fn constructor() -> NodeConstructor {
1309        NodeConstructor::new::<Self>()
1310            .with_variant("Tile Map", |_| {
1311                TileMapBuilder::new(BaseBuilder::new().with_name("Tile Map"))
1312                    .build_node()
1313                    .into()
1314            })
1315            .with_group("2D")
1316    }
1317}
1318
1319impl NodeTrait for TileMap {
1320    fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
1321        let Some(rect) = *self.bounding_rect() else {
1322            return AxisAlignedBoundingBox::default();
1323        };
1324
1325        let mut min_pos = rect.position.cast::<f32>().to_homogeneous();
1326        let mut max_pos = (rect.position + rect.size).cast::<f32>().to_homogeneous();
1327        min_pos.x *= -1.0;
1328        max_pos.x *= -1.0;
1329        let (min, max) = min_pos.inf_sup(&max_pos);
1330
1331        AxisAlignedBoundingBox::from_min_max(min, max)
1332    }
1333
1334    fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
1335        self.local_bounding_box()
1336            .transform(&self.global_transform())
1337    }
1338
1339    fn id(&self) -> Uuid {
1340        Self::type_uuid()
1341    }
1342
1343    fn collect_render_data(&self, ctx: &mut RenderContext) -> RdcControlFlow {
1344        if !self.should_be_rendered(ctx.frustum) {
1345            return RdcControlFlow::Continue;
1346        }
1347
1348        if renderer::is_shadow_pass(ctx.render_pass_name) {
1349            return RdcControlFlow::Continue;
1350        }
1351
1352        let Some(ref tile_set_resource) = *self.tile_set else {
1353            return RdcControlFlow::Continue;
1354        };
1355
1356        let mut tile_set_lock = TileSetRef::new(tile_set_resource);
1357        let tile_set = tile_set_lock.as_loaded();
1358
1359        let mut hidden_tiles = self.hidden_tiles.lock();
1360        hidden_tiles.clear();
1361
1362        let bounds = ctx
1363            .frustum
1364            .as_ref()
1365            .map(|f| self.cells_touching_frustum(f))
1366            .unwrap_or_default();
1367
1368        let mut tile_render_context = TileMapRenderContext {
1369            tile_map_handle: self.handle(),
1370            transform: self.tile_map_transform(),
1371            hidden_tiles: &mut hidden_tiles,
1372            context: ctx,
1373            bounds,
1374            tile_set,
1375        };
1376
1377        for effect in self.before_effects.iter() {
1378            effect.lock().render_special_tiles(&mut tile_render_context);
1379        }
1380        let bounds = tile_render_context.visible_bounds();
1381        let Some(tiles) = self.tiles.as_ref().map(|r| r.data_ref()) else {
1382            return RdcControlFlow::Continue;
1383        };
1384        let Some(tiles) = tiles.as_loaded_ref() else {
1385            return RdcControlFlow::Continue;
1386        };
1387        if bounds.is_some() {
1388            for (position, handle) in tiles.bounded_iter(bounds) {
1389                if bounds.contains(position) && tile_render_context.is_tile_visible(position) {
1390                    let handle = tile_render_context.get_animated_version(handle);
1391                    tile_render_context.draw_tile(position, handle);
1392                }
1393            }
1394        } else {
1395            for (position, handle) in tiles.iter() {
1396                if tile_render_context.is_tile_visible(position) {
1397                    let handle = tile_render_context.get_animated_version(handle);
1398                    tile_render_context.draw_tile(position, handle);
1399                }
1400            }
1401        }
1402        for effect in self.after_effects.iter() {
1403            effect.lock().render_special_tiles(&mut tile_render_context);
1404        }
1405        RdcControlFlow::Continue
1406    }
1407
1408    fn validate(&self, _scene: &Scene) -> Result<(), String> {
1409        if self.tile_set.is_none() {
1410            Err(
1411                "Tile set resource is not set. Tile map will not be rendered correctly!"
1412                    .to_string(),
1413            )
1414        } else {
1415            Ok(())
1416        }
1417    }
1418}
1419
1420/// Tile map builder allows you to create [`TileMap`] scene nodes.
1421pub struct TileMapBuilder {
1422    base_builder: BaseBuilder,
1423    tile_set: Option<TileSetResource>,
1424    tiles: TileMapData,
1425    tile_scale: Vector2<f32>,
1426    before_effects: Vec<TileMapEffectRef>,
1427    after_effects: Vec<TileMapEffectRef>,
1428}
1429
1430impl TileMapBuilder {
1431    /// Creates new tile map builder.
1432    pub fn new(base_builder: BaseBuilder) -> Self {
1433        Self {
1434            base_builder,
1435            tile_set: None,
1436            tiles: TileMapData::default(),
1437            tile_scale: Vector2::repeat(1.0),
1438            before_effects: Default::default(),
1439            after_effects: Default::default(),
1440        }
1441    }
1442
1443    /// Sets the desired tile set.
1444    pub fn with_tile_set(mut self, tile_set: TileSetResource) -> Self {
1445        self.tile_set = Some(tile_set);
1446        self
1447    }
1448
1449    /// Sets the actual tiles of the tile map.
1450    pub fn with_tiles(mut self, tiles: &Tiles) -> Self {
1451        for (pos, handle) in tiles.iter() {
1452            self.tiles.set(*pos, *handle);
1453        }
1454        self
1455    }
1456
1457    /// Sets the actual tile scaling.
1458    pub fn with_tile_scale(mut self, tile_scale: Vector2<f32>) -> Self {
1459        self.tile_scale = tile_scale;
1460        self
1461    }
1462
1463    /// Adds an effect to the tile map which will run before the tiles render.
1464    pub fn with_before_effect(mut self, effect: TileMapEffectRef) -> Self {
1465        self.before_effects.push(effect);
1466        self
1467    }
1468
1469    /// Adds an effect to the tile map which will run after the tiles render.
1470    pub fn with_after_effect(mut self, effect: TileMapEffectRef) -> Self {
1471        self.after_effects.push(effect);
1472        self
1473    }
1474
1475    /// Builds tile map scene node, but not adds it to a scene graph.
1476    pub fn build_node(self) -> Node {
1477        Node::new(TileMap {
1478            base: self.base_builder.build_base(),
1479            tile_set: self.tile_set.into(),
1480            tiles: Some(Resource::new_ok(ResourceKind::Embedded, self.tiles)).into(),
1481            tile_scale: self.tile_scale.into(),
1482            active_brush: Default::default(),
1483            hidden_tiles: Mutex::default(),
1484            before_effects: self.before_effects,
1485            after_effects: self.after_effects,
1486        })
1487    }
1488
1489    /// Finishes tile map building and adds it to the specified scene graph.
1490    pub fn build(self, graph: &mut Graph) -> Handle<Node> {
1491        graph.add_node(self.build_node())
1492    }
1493}