Skip to main content

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