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::lazy_static::*;
55use crate::{
56    asset::{untyped::ResourceKind, ResourceDataRef},
57    core::{
58        algebra::{Matrix4, Vector2, Vector3},
59        color::Color,
60        math::{aabb::AxisAlignedBoundingBox, Matrix4Ext, TriangleDefinition},
61        pool::Handle,
62        reflect::prelude::*,
63        type_traits::prelude::*,
64        variable::InheritableVariable,
65        visitor::prelude::*,
66        ImmutableString, SafeLock,
67    },
68    graph::{constructor::ConstructorProvider, BaseSceneGraph},
69    material::{Material, MaterialResource, STANDARD_2D},
70    renderer::{self, bundle::RenderContext},
71    scene::{
72        base::{Base, BaseBuilder},
73        graph::Graph,
74        mesh::{
75            buffer::{
76                VertexAttributeDataType, VertexAttributeDescriptor, VertexAttributeUsage,
77                VertexTrait,
78            },
79            RenderPath,
80        },
81        node::{Node, NodeTrait, RdcControlFlow},
82        Scene,
83    },
84};
85use bytemuck::{Pod, Zeroable};
86use fyrox_resource::manager::ResourceManager;
87use std::{
88    error::Error,
89    fmt::Display,
90    ops::{Deref, DerefMut},
91    path::PathBuf,
92};
93
94/// Current implementation version marker.
95pub const VERSION: u8 = 1;
96
97lazy_static! {
98    /// The default material for tiles that have no material set.
99    pub static ref DEFAULT_TILE_MATERIAL: MaterialResource = 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, 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 Display for ResourceTilePosition {
415    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
416        match self {
417            ResourceTilePosition::Page(p) => write!(f, "Page({},{})", p.x, p.y),
418            ResourceTilePosition::Tile(page, pos) => {
419                write!(f, "({},{}):({},{})", page.x, page.y, pos.x, pos.y)
420            }
421        }
422    }
423}
424
425impl From<TileDefinitionHandle> for ResourceTilePosition {
426    fn from(value: TileDefinitionHandle) -> Self {
427        Self::Tile(value.page(), value.tile())
428    }
429}
430
431impl ResourceTilePosition {
432    /// Construct a position from the given stage, page, and tile.
433    /// If the stage is [`TilePaletteStage::Pages`] then this position is refering to some page
434    /// as if it were a tile, and therefore the `page` argument is ignored and the `tile` argument
435    /// is taken as the page's position.
436    pub fn new(stage: TilePaletteStage, page: Vector2<i32>, tile: Vector2<i32>) -> Self {
437        match stage {
438            TilePaletteStage::Pages => Self::Page(tile),
439            TilePaletteStage::Tiles => Self::Tile(page, tile),
440        }
441    }
442    /// This position refers to some page.
443    pub fn is_page(&self) -> bool {
444        matches!(self, Self::Page(_))
445    }
446    /// This position refers to a tile within a page.
447    pub fn is_tile(&self) -> bool {
448        matches!(self, Self::Tile(_, _))
449    }
450    /// The stage that contains this position.
451    pub fn stage(&self) -> TilePaletteStage {
452        match self {
453            Self::Page(_) => TilePaletteStage::Pages,
454            Self::Tile(_, _) => TilePaletteStage::Tiles,
455        }
456    }
457    /// The position within the stage. For a page position, this is the page's coordinates.
458    /// For a tile position, this is the tile's coordinates.
459    pub fn stage_position(&self) -> Vector2<i32> {
460        match self {
461            Self::Page(p) => *p,
462            Self::Tile(_, p) => *p,
463        }
464    }
465    /// The page coordinates of the position. For a page position, this is
466    pub fn page(&self) -> Vector2<i32> {
467        match self {
468            Self::Page(p) => *p,
469            Self::Tile(p, _) => *p,
470        }
471    }
472    /// The handle associated with this position, if this is a tile position.
473    pub fn handle(&self) -> Option<TileDefinitionHandle> {
474        if let Self::Tile(p, t) = self {
475            TileDefinitionHandle::try_new(*p, *t)
476        } else {
477            None
478        }
479    }
480}
481
482/// Tile is a base block of a tile map. It has a position and a handle of tile definition, stored
483/// in the respective tile set.
484#[derive(Clone, Reflect, Default, Debug, PartialEq, Visit, ComponentProvider, TypeUuidProvider)]
485#[type_uuid(id = "e429ca1b-a311-46c3-b580-d5a2f49db7e2")]
486pub struct Tile {
487    /// Position of the tile (in grid coordinates).
488    pub position: Vector2<i32>,
489    /// A handle of the tile definition.
490    pub definition_handle: TileDefinitionHandle,
491}
492
493/// Adapt an iterator over positions into an iterator over `(Vector2<i32>, TileHandleDefinition)`.
494#[derive(Debug, Clone)]
495pub struct TileIter<I> {
496    source: TileBook,
497    stage: TilePaletteStage,
498    page: Vector2<i32>,
499    positions: I,
500}
501
502impl<I: Iterator<Item = Vector2<i32>>> Iterator for TileIter<I> {
503    type Item = (Vector2<i32>, TileDefinitionHandle);
504
505    fn next(&mut self) -> Option<Self::Item> {
506        self.positions.find_map(|p| {
507            let h = self
508                .source
509                .get_tile_handle(ResourceTilePosition::new(self.stage, self.page, p))?;
510            Some((p, h))
511        })
512    }
513}
514
515#[derive(Debug, Default, Clone, PartialEq, Visit, Reflect)]
516/// Abstract source of tiles, which can either be a tile set or a brush.
517/// It is called a "book" because each of these tile resources contains
518/// pages of tiles.
519pub enum TileBook {
520    /// A tile resource containing no tiles.
521    #[default]
522    Empty,
523    /// Getting tiles from a tile set
524    TileSet(TileSetResource),
525    /// Getting tiles from a brush
526    Brush(TileMapBrushResource),
527}
528
529impl TileBook {
530    /// The TileDefinitionHandle of the icon that represents the page at the given position.
531    #[inline]
532    pub fn page_icon(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
533        match self {
534            TileBook::Empty => None,
535            TileBook::TileSet(r) => r.state().data()?.page_icon(position),
536            TileBook::Brush(r) => r.state().data()?.page_icon(position),
537        }
538    }
539    /// Returns true if this resource is a tile set.
540    #[inline]
541    pub fn is_tile_set(&self) -> bool {
542        matches!(self, TileBook::TileSet(_))
543    }
544    /// Returns true if this resource is a brush.
545    #[inline]
546    pub fn is_brush(&self) -> bool {
547        matches!(self, TileBook::Brush(_))
548    }
549    /// Returns true if this contains no resource.
550    #[inline]
551    pub fn is_empty(&self) -> bool {
552        matches!(self, TileBook::Empty)
553    }
554    /// Return the path of the resource as a String.
555    pub fn name(&self, resource_manager: &ResourceManager) -> String {
556        self.path(resource_manager)
557            .map(|x| x.to_string_lossy().into_owned())
558            .unwrap_or_else(|| "Error".into())
559    }
560    /// Return the path of the resource.
561    pub fn path(&self, resource_manager: &ResourceManager) -> Option<PathBuf> {
562        match self {
563            TileBook::Empty => None,
564            TileBook::TileSet(r) => resource_manager.resource_path(r.as_ref()),
565            TileBook::Brush(r) => resource_manager.resource_path(r.as_ref()),
566        }
567    }
568    /// True if the resource is external and its `change_count` is not zero.
569    pub fn needs_save(&self) -> bool {
570        match self {
571            TileBook::Empty => false,
572            TileBook::TileSet(r) => {
573                r.header().kind.is_external() && r.data_ref().change_flag.needs_save()
574            }
575            TileBook::Brush(r) => {
576                r.header().kind.is_external() && r.data_ref().change_flag.needs_save()
577            }
578        }
579    }
580    /// Attempt to save the resource to its file, if it has one and if `change_flag` is set.
581    /// Otherwise do nothing and return Ok to indicate success.
582    pub fn save(&self, resource_manager: &ResourceManager) -> Result<(), Box<dyn Error>> {
583        match self {
584            TileBook::Empty => Ok(()),
585            TileBook::TileSet(r) => {
586                if r.header().kind.is_external() && r.data_ref().change_flag.needs_save() {
587                    let result = r.save(&resource_manager.resource_path(r.as_ref()).unwrap());
588                    if result.is_ok() {
589                        r.data_ref().change_flag.reset();
590                    }
591                    result
592                } else {
593                    Ok(())
594                }
595            }
596            TileBook::Brush(r) => {
597                if r.header().kind.is_external() && r.data_ref().change_flag.needs_save() {
598                    let result = r.save(&resource_manager.resource_path(r.as_ref()).unwrap());
599                    if result.is_ok() {
600                        r.data_ref().change_flag.reset();
601                    }
602                    result
603                } else {
604                    Ok(())
605                }
606            }
607        }
608    }
609    /// A reference to the TileSetResource, if this is a TileSetResource.
610    pub fn tile_set_ref(&self) -> Option<&TileSetResource> {
611        match self {
612            TileBook::TileSet(r) => Some(r),
613            _ => None,
614        }
615    }
616    /// A reference to the TileMapBrushResource, if this is a TileMapBrushResource.
617    pub fn brush_ref(&self) -> Option<&TileMapBrushResource> {
618        match self {
619            TileBook::Brush(r) => Some(r),
620            _ => None,
621        }
622    }
623    /// Returns the tile set associated with this resource.
624    /// If the resource is a tile set, the return that tile set.
625    /// If the resource is a brush, then return the tile set used by that brush.
626    pub fn get_tile_set(&self) -> Option<TileSetResource> {
627        match self {
628            TileBook::Empty => None,
629            TileBook::TileSet(r) => Some(r.clone()),
630            TileBook::Brush(r) => r.state().data()?.tile_set(),
631        }
632    }
633    /// Build a list of the positions of all tiles on the given page.
634    pub fn get_all_tile_positions(&self, page: Vector2<i32>) -> Vec<Vector2<i32>> {
635        match self {
636            TileBook::Empty => Vec::new(),
637            TileBook::TileSet(r) => r
638                .state()
639                .data()
640                .map(|r| r.keys_on_page(page))
641                .unwrap_or_default(),
642            TileBook::Brush(r) => r
643                .state()
644                .data()
645                .and_then(|r| {
646                    r.pages
647                        .get(&page)
648                        .map(|p| p.tiles.keys().copied().collect())
649                })
650                .unwrap_or_default(),
651        }
652    }
653    /// Build a list of the posiitons of all pages.
654    pub fn get_all_page_positions(&self) -> Vec<Vector2<i32>> {
655        match self {
656            TileBook::Empty => Vec::new(),
657            TileBook::TileSet(r) => r.state().data().map(|r| r.page_keys()).unwrap_or_default(),
658            TileBook::Brush(r) => r
659                .state()
660                .data()
661                .map(|r| r.pages.keys().copied().collect())
662                .unwrap_or_default(),
663        }
664    }
665    /// True if there is a page at the given position.
666    pub fn has_page_at(&self, position: Vector2<i32>) -> bool {
667        match self {
668            TileBook::Empty => false,
669            TileBook::TileSet(r) => r
670                .state()
671                .data()
672                .map(|r| r.pages.contains_key(&position))
673                .unwrap_or(false),
674            TileBook::Brush(r) => r
675                .state()
676                .data()
677                .map(|r| r.pages.contains_key(&position))
678                .unwrap_or(false),
679        }
680    }
681    /// The type of the page at the given position, if there is one.
682    pub fn page_type(&self, position: Vector2<i32>) -> Option<PageType> {
683        match self {
684            TileBook::Empty => None,
685            TileBook::TileSet(r) => r.state().data()?.get_page(position).map(|p| p.page_type()),
686            TileBook::Brush(r) => {
687                if r.state().data()?.has_page_at(position) {
688                    Some(PageType::Brush)
689                } else {
690                    None
691                }
692            }
693        }
694    }
695    /// True if there is a atlas page at the given coordinates.
696    pub fn is_atlas_page(&self, position: Vector2<i32>) -> bool {
697        self.page_type(position) == Some(PageType::Atlas)
698    }
699    /// True if there is a free tile page at the given coordinates.
700    pub fn is_free_page(&self, position: Vector2<i32>) -> bool {
701        self.page_type(position) == Some(PageType::Freeform)
702    }
703    /// True if there is a transform page at the given coordinates.
704    pub fn is_transform_page(&self, position: Vector2<i32>) -> bool {
705        self.page_type(position) == Some(PageType::Transform)
706    }
707    /// True if there is a transform page at the given coordinates.
708    pub fn is_animation_page(&self, position: Vector2<i32>) -> bool {
709        self.page_type(position) == Some(PageType::Animation)
710    }
711    /// True if there is a brush page at the given coordinates.
712    pub fn is_brush_page(&self, position: Vector2<i32>) -> bool {
713        self.page_type(position) == Some(PageType::Brush)
714    }
715    /// Return true if there is a tile at the given position on the page at the given position.
716    pub fn has_tile_at(&self, page: Vector2<i32>, tile: Vector2<i32>) -> bool {
717        match self {
718            TileBook::Empty => false,
719            TileBook::TileSet(r) => r
720                .state()
721                .data()
722                .map(|r| r.has_tile_at(page, tile))
723                .unwrap_or(false),
724            TileBook::Brush(r) => r
725                .state()
726                .data()
727                .map(|r| r.has_tile_at(page, tile))
728                .unwrap_or(false),
729        }
730    }
731    /// Returns the TileDefinitionHandle that points to the data in the tile set that represents this tile.
732    /// Even if this resource is actually a brush, the handle returned still refers to some page and position
733    /// in the brush's tile set.
734    pub fn get_tile_handle(&self, position: ResourceTilePosition) -> Option<TileDefinitionHandle> {
735        match self {
736            TileBook::Empty => None,
737            TileBook::TileSet(r) => r.state().data()?.redirect_handle(position),
738            TileBook::Brush(r) => r.state().data()?.redirect_handle(position),
739        }
740    }
741    /// The StampElement for the given position in this resource.
742    /// For brushes, the [`StampElement::handle`] refers to the ultimate location of the tile within the
743    /// tile set, while the [`StampElement::source`] refers to the location of the tile within
744    /// the brush or tile set that was used to create the stamp.
745    pub fn get_stamp_element(&self, position: ResourceTilePosition) -> Option<StampElement> {
746        match self {
747            TileBook::Empty => None,
748            TileBook::TileSet(r) => r.state().data()?.stamp_element(position),
749            TileBook::Brush(r) => r.state().data()?.stamp_element(position),
750        }
751    }
752    /// Returns an iterator over `(Vector2<i32>, TileDefinitionHandle)` where the first
753    /// member of the pair is the position of the tile on the page as provided by `positions`
754    /// and the second member is the handle that would be returned from [`get_tile_handle`](Self::get_tile_handle).
755    pub fn get_tile_iter<I: Iterator<Item = Vector2<i32>>>(
756        &self,
757        stage: TilePaletteStage,
758        page: Vector2<i32>,
759        positions: I,
760    ) -> TileIter<I> {
761        TileIter {
762            source: self.clone(),
763            stage,
764            page,
765            positions,
766        }
767    }
768    /// Construct a Tiles object holding the tile definition handles for the tiles
769    /// at the given positions on the given page.
770    pub fn get_tiles<I: Iterator<Item = Vector2<i32>>>(
771        &self,
772        stage: TilePaletteStage,
773        page: Vector2<i32>,
774        iter: I,
775        tiles: &mut Tiles,
776    ) {
777        match self {
778            TileBook::Empty => (),
779            TileBook::TileSet(res) => {
780                if let Some(tile_set) = res.state().data() {
781                    tile_set.get_tiles(stage, page, iter, tiles);
782                }
783            }
784            TileBook::Brush(res) => {
785                if let Some(brush) = res.state().data() {
786                    brush.get_tiles(stage, page, iter, tiles);
787                }
788            }
789        }
790    }
791
792    /// Returns true if the resource is a brush that has no tile set.
793    pub fn is_missing_tile_set(&self) -> bool {
794        match self {
795            TileBook::Empty => false,
796            TileBook::TileSet(_) => false,
797            TileBook::Brush(resource) => resource
798                .state()
799                .data()
800                .map(|b| b.is_missing_tile_set())
801                .unwrap_or(false),
802        }
803    }
804
805    /// Return the `TileRenderData` needed to render the tile at the given position on the given page.
806    /// If there is no tile at that position or the tile set is missing or not loaded, then None is returned.
807    /// If there is a tile and a tile set, but the handle of the tile does not exist in the tile set,
808    /// then the rendering data for an error tile is returned using `TileRenderData::missing_tile()`.
809    ///
810    /// Beware that this method is *slow.* Like most methods in `TileBook`, this method involves
811    /// locking a resource, so none of them should be called many times per frame, as one might be
812    /// tempted to do with this method. Do *not* render a `TileBook` by repeatedly calling this
813    /// method. Use [`tile_render_loop`](Self::tile_render_loop) instead.
814    pub fn get_tile_render_data(&self, position: ResourceTilePosition) -> Option<TileRenderData> {
815        match self {
816            TileBook::Empty => None,
817            TileBook::TileSet(resource) => resource.state().data()?.get_tile_render_data(position),
818            TileBook::Brush(resource) => resource.state().data()?.get_tile_render_data(position),
819        }
820    }
821
822    /// Repeatedly call the given function with each tile for the given stage and page.
823    /// The function is given the position of the tile within the palette and the
824    /// data for rendering the tile.
825    pub fn tile_render_loop<F>(&self, stage: TilePaletteStage, page: Vector2<i32>, func: F)
826    where
827        F: FnMut(Vector2<i32>, TileRenderData),
828    {
829        match self {
830            TileBook::Empty => (),
831            TileBook::TileSet(res) => {
832                if let Some(data) = res.state().data() {
833                    data.palette_render_loop(stage, page, func)
834                }
835            }
836            TileBook::Brush(res) => {
837                if let Some(data) = res.state().data() {
838                    data.palette_render_loop(stage, page, func)
839                }
840            }
841        };
842    }
843    /// Repeatedly call the given function with each collider for each tile on the given page.
844    /// The function is given the position of the tile
845    pub fn tile_collider_loop<F>(&self, page: Vector2<i32>, func: F)
846    where
847        F: FnMut(Vector2<i32>, Uuid, Color, &TileCollider),
848    {
849        match self {
850            TileBook::Empty => (),
851            TileBook::TileSet(res) => {
852                if let Some(data) = res.state().data() {
853                    data.tile_collider_loop(page, func)
854                }
855            }
856            TileBook::Brush(_) => (),
857        };
858    }
859    /// Returns the rectangle within a material that a tile should show
860    /// at the given stage and handle.
861    pub fn get_tile_bounds(&self, position: ResourceTilePosition) -> Option<TileMaterialBounds> {
862        match self {
863            TileBook::Empty => None,
864            TileBook::TileSet(res) => res
865                .state()
866                .data()
867                .map(|d| d.get_tile_bounds(position))
868                .unwrap_or_default(),
869            TileBook::Brush(res) => res
870                .state()
871                .data()
872                .map(|d| d.get_tile_bounds(position))
873                .unwrap_or_default(),
874        }
875    }
876    /// The bounds of the tiles on the given page.
877    pub fn tiles_bounds(&self, stage: TilePaletteStage, page: Vector2<i32>) -> OptionTileRect {
878        match self {
879            TileBook::Empty => OptionTileRect::default(),
880            TileBook::TileSet(res) => res.data_ref().tiles_bounds(stage, page),
881            TileBook::Brush(res) => res.data_ref().tiles_bounds(stage, page),
882        }
883    }
884}
885
886/// The specification for how to render a tile.
887#[derive(Clone, Default, Debug)]
888pub struct TileRenderData {
889    /// The material to use to render this tile.
890    pub material_bounds: Option<TileMaterialBounds>,
891    /// The color to use to render the tile
892    pub color: Color,
893    /// This data represents the empty tile
894    empty: bool,
895}
896
897impl TileRenderData {
898    /// Create a TileRenderData
899    pub fn new(material_bounds: Option<TileMaterialBounds>, color: Color) -> Self {
900        Self {
901            material_bounds,
902            color,
903            empty: false,
904        }
905    }
906    /// Create an empty TileRenderData
907    pub fn empty() -> Self {
908        Self {
909            material_bounds: None,
910            color: Color::WHITE,
911            empty: true,
912        }
913    }
914    /// True if the render data is for the empty tile.
915    pub fn is_empty(&self) -> bool {
916        self.empty
917    }
918    /// Returns TileRenderData to represent an error due to render data being unavailable.
919    pub fn missing_data() -> Self {
920        Self {
921            material_bounds: None,
922            color: Color::HOT_PINK,
923            empty: false,
924        }
925    }
926}
927
928impl OrthoTransform for TileRenderData {
929    fn x_flipped(mut self) -> Self {
930        self.material_bounds = self.material_bounds.map(|b| b.x_flipped());
931        self
932    }
933
934    fn rotated(mut self, amount: i8) -> Self {
935        self.material_bounds = self.material_bounds.map(|b| b.rotated(amount));
936        self
937    }
938}
939
940/// Tile map is a 2D "image", made out of a small blocks called tiles. Tile maps used in 2D games to
941/// build game worlds quickly and easily. Each tile is represented by a [`TileDefinitionHandle`] which
942/// contains the position of a page and the position of a tile within that page.
943///
944/// When rendering the `TileMap`, the rendering data is fetched from the tile map's tile set resource,
945/// which contains all the pages that may be referenced by the tile map's handles.
946///
947/// Optional [`TileMapEffect`] objects may be included in the `TileMap` to change how it renders.
948#[derive(Reflect, Debug, ComponentProvider, TypeUuidProvider)]
949#[reflect(derived_type = "Node")]
950#[type_uuid(id = "aa9a3385-a4af-4faf-a69a-8d3af1a3aa67")]
951pub struct TileMap {
952    base: Base,
953    /// The source of rendering data for tiles in this tile map.
954    tile_set: InheritableVariable<Option<TileSetResource>>,
955    /// Tile container of the tile map.
956    #[reflect(hidden)]
957    pub tiles: InheritableVariable<Option<TileMapDataResource>>,
958    tile_scale: InheritableVariable<Vector2<f32>>,
959    active_brush: InheritableVariable<Option<TileMapBrushResource>>,
960    /// Temporary space to store which tiles are invisible during `collect_render_data`.
961    /// This is part of how [`TileMapEffect`] can prevent a tile from being rendered.
962    #[reflect(hidden)]
963    hidden_tiles: Mutex<FxHashSet<Vector2<i32>>>,
964    /// Special rendering effects that may change how the tile map renders.
965    /// These effects are processed in order before the tile map performs the
966    /// normal rendering of tiles, and they can prevent some times from being
967    /// rendered and render other tiles in place of what would normally be
968    /// rendered.
969    #[reflect(hidden)]
970    pub before_effects: Vec<TileMapEffectRef>,
971    /// Special rendering effects that may change how the tile map renders.
972    /// These effects are processed in order after the tile map performs the
973    /// normal rendering of tiles.
974    #[reflect(hidden)]
975    pub after_effects: Vec<TileMapEffectRef>,
976}
977
978impl Visit for TileMap {
979    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
980        let mut region = visitor.enter_region(name)?;
981        let mut version = if region.is_reading() { 0 } else { VERSION };
982        let _ = version.visit("Version", &mut region);
983        self.base.visit("Base", &mut region)?;
984        self.tile_set.visit("TileSet", &mut region)?;
985        self.tile_scale.visit("TileScale", &mut region)?;
986        self.active_brush.visit("ActiveBrush", &mut region)?;
987        match version {
988            0 => {
989                let mut tiles = InheritableVariable::new_non_modified(Tiles::default());
990                let result = tiles.visit("Tiles", &mut region);
991                result?;
992                let mut data = TileMapData::default();
993                for (p, h) in tiles.iter() {
994                    data.set(*p, *h);
995                }
996                self.tiles = Some(Resource::new_ok(
997                    Uuid::new_v4(),
998                    ResourceKind::Embedded,
999                    data,
1000                ))
1001                .into();
1002            }
1003            VERSION => {
1004                self.tiles.visit("Tiles", &mut region)?;
1005            }
1006            _ => return Err(VisitError::User("Unknown version".into())),
1007        }
1008        Ok(())
1009    }
1010}
1011
1012/// A reference to the tile data of a some tile in a tile set.
1013pub struct TileMapDataRef<'a> {
1014    tile_set: ResourceDataRef<'a, TileSet>,
1015    handle: TileDefinitionHandle,
1016}
1017
1018impl Deref for TileMapDataRef<'_> {
1019    type Target = TileData;
1020
1021    fn deref(&self) -> &Self::Target {
1022        self.tile_set.tile_data(self.handle).unwrap()
1023    }
1024}
1025
1026/// An error in finding a property for a tile.
1027#[derive(Debug)]
1028pub enum TilePropertyError {
1029    /// The tile map has no tile set, so no tile data is available.
1030    MissingTileSet,
1031    /// The tile map has a tile set, but it is not yet loaded.
1032    TileSetNotLoaded,
1033    /// There is no property with the given name in the tile set.
1034    UnrecognizedName(ImmutableString),
1035    /// There is no property with the given UUID in the tile set.
1036    UnrecognizedUuid(Uuid),
1037    /// The property has the wrong type.
1038    WrongType(&'static str),
1039}
1040
1041impl Display for TilePropertyError {
1042    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1043        match self {
1044            TilePropertyError::MissingTileSet => write!(f, "The tile map has no tile set."),
1045            TilePropertyError::TileSetNotLoaded => {
1046                write!(f, "The tile map's tile set is not loaded.")
1047            }
1048            TilePropertyError::UnrecognizedName(name) => {
1049                write!(f, "There is no property with this name: {name}")
1050            }
1051            TilePropertyError::UnrecognizedUuid(uuid) => {
1052                write!(f, "There is no property with this UUID: {uuid}")
1053            }
1054            TilePropertyError::WrongType(message) => write!(f, "Property type error: {message}"),
1055        }
1056    }
1057}
1058
1059impl Error for TilePropertyError {}
1060
1061impl TileMap {
1062    /// The handle that is stored in the tile map at the given position to refer to some tile in the tile set.
1063    pub fn tile_handle(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
1064        let tiles = self.tiles.as_ref()?.data_ref();
1065        tiles.as_loaded_ref()?.get(position)
1066    }
1067    /// The tile data for the tile at the given position, if that position has a tile and this tile map
1068    /// has a tile set that contains data for the tile's handle.
1069    pub fn tile_data(&self, position: Vector2<i32>) -> Option<TileMapDataRef> {
1070        let handle = self.tile_handle(position)?;
1071        let tile_set = self.tile_set.as_ref()?.data_ref();
1072        if tile_set.as_loaded_ref()?.tile_data(handle).is_some() {
1073            Some(TileMapDataRef { tile_set, handle })
1074        } else {
1075            None
1076        }
1077    }
1078    /// The property value for the property of the given name for the tile at the given position in this tile map.
1079    /// This requires that the tile map has a loaded tile set and the tile set contains a property with the given name.
1080    /// Otherwise an error is returned to indicate which of these conditions failed.
1081    /// If the only problem is that there is no tile at the given position, then the default value for the property's value type
1082    /// is returned.
1083    pub fn tile_property_value<T>(
1084        &self,
1085        position: Vector2<i32>,
1086        property_id: Uuid,
1087    ) -> Result<T, TilePropertyError>
1088    where
1089        T: TryFrom<TileSetPropertyValue, Error = TilePropertyError> + Default,
1090    {
1091        let Some(handle) = self.tile_handle(position) else {
1092            return Ok(T::default());
1093        };
1094        let tile_set = self
1095            .tile_set
1096            .as_ref()
1097            .ok_or(TilePropertyError::MissingTileSet)?
1098            .data_ref();
1099        let tile_set = tile_set
1100            .as_loaded_ref()
1101            .ok_or(TilePropertyError::TileSetNotLoaded)?;
1102        tile_set.tile_property_value(handle, property_id)
1103    }
1104    /// The property value for the property of the given name for the tile at the given position in this tile map.
1105    /// This requires that the tile map has a loaded tile set and the tile set contains a property with the given name.
1106    /// Otherwise an error is returned to indicate which of these conditions failed.
1107    /// If the only problem is that there is no tile at the given position, then the default value for the property's value type
1108    /// is returned.
1109    pub fn tile_property_value_by_name(
1110        &self,
1111        position: Vector2<i32>,
1112        property_name: &ImmutableString,
1113    ) -> Result<TileSetPropertyValue, TilePropertyError> {
1114        let tile_set = self
1115            .tile_set
1116            .as_ref()
1117            .ok_or(TilePropertyError::MissingTileSet)?
1118            .data_ref();
1119        let tile_set = tile_set
1120            .as_loaded_ref()
1121            .ok_or(TilePropertyError::TileSetNotLoaded)?;
1122        let property = tile_set
1123            .find_property_by_name(property_name)
1124            .ok_or_else(|| TilePropertyError::UnrecognizedName(property_name.clone()))?;
1125        let Some(handle) = self.tile_handle(position) else {
1126            return Ok(property.prop_type.default_value());
1127        };
1128        Ok(tile_set
1129            .property_value(handle, property.uuid)
1130            .unwrap_or_else(|| property.prop_type.default_value()))
1131    }
1132    /// The property value for the property of the given UUID for the tile at the given position in this tile map.
1133    /// This requires that the tile map has a loaded tile set and the tile set contains a property with the given UUID.
1134    /// Otherwise an error is returned to indicate which of these conditions failed.
1135    /// If the only problem is that there is no tile at the given position, then the default value for the property's value type
1136    /// is returned.
1137    pub fn tile_property_value_by_uuid_untyped(
1138        &self,
1139        position: Vector2<i32>,
1140        property_id: Uuid,
1141    ) -> Result<TileSetPropertyValue, TilePropertyError> {
1142        let tile_set = self
1143            .tile_set
1144            .as_ref()
1145            .ok_or(TilePropertyError::MissingTileSet)?
1146            .data_ref();
1147        let tile_set = tile_set
1148            .as_loaded_ref()
1149            .ok_or(TilePropertyError::TileSetNotLoaded)?;
1150        let value = if let Some(handle) = self.tile_handle(position) {
1151            tile_set
1152                .tile_data(handle)
1153                .and_then(|d| d.properties.get(&property_id))
1154                .cloned()
1155        } else {
1156            None
1157        };
1158        if let Some(value) = value {
1159            Ok(value)
1160        } else {
1161            let property = tile_set
1162                .find_property(property_id)
1163                .ok_or(TilePropertyError::UnrecognizedUuid(property_id))?;
1164            Ok(property.prop_type.default_value())
1165        }
1166    }
1167    /// The global transform of the tile map with initial x-axis flip applied, so the positive x-axis points left instead of right.
1168    pub fn tile_map_transform(&self) -> Matrix4<f32> {
1169        self.global_transform()
1170            .prepend_nonuniform_scaling(&Vector3::new(-1.0, 1.0, 1.0))
1171    }
1172    /// Returns a reference to the current tile set (if any).
1173    #[inline]
1174    pub fn tile_set(&self) -> Option<&TileSetResource> {
1175        self.tile_set.as_ref()
1176    }
1177
1178    /// Sets new tile set.
1179    #[inline]
1180    pub fn set_tile_set(&mut self, tile_set: Option<TileSetResource>) {
1181        self.tile_set.set_value_and_mark_modified(tile_set);
1182    }
1183
1184    /// Returns a reference to the tile container.
1185    #[inline]
1186    pub fn tiles(&self) -> Option<&TileMapDataResource> {
1187        self.tiles.as_ref()
1188    }
1189
1190    /// Sets new tiles.
1191    #[inline]
1192    pub fn set_tiles(&mut self, tiles: TileMapDataResource) {
1193        self.tiles.set_value_and_mark_modified(Some(tiles));
1194    }
1195
1196    /// Returns current tile scaling.
1197    #[inline]
1198    pub fn tile_scale(&self) -> Vector2<f32> {
1199        *self.tile_scale
1200    }
1201
1202    /// Sets new tile scaling, which defines tile size.
1203    #[inline]
1204    pub fn set_tile_scale(&mut self, tile_scale: Vector2<f32>) {
1205        self.tile_scale.set_value_and_mark_modified(tile_scale);
1206    }
1207
1208    /// Inserts a tile in the tile map. Returns previous tile, located at the same position as
1209    /// the new one (if any).
1210    #[inline]
1211    pub fn insert_tile(
1212        &mut self,
1213        position: Vector2<i32>,
1214        tile: TileDefinitionHandle,
1215    ) -> Option<TileDefinitionHandle> {
1216        self.tiles
1217            .as_ref()?
1218            .data_ref()
1219            .as_loaded_mut()?
1220            .replace(position, Some(tile))
1221    }
1222
1223    /// Removes a tile from the tile map.
1224    #[inline]
1225    pub fn remove_tile(&mut self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
1226        self.tiles
1227            .as_ref()?
1228            .data_ref()
1229            .as_loaded_mut()?
1230            .replace(position, None)
1231    }
1232
1233    /// Returns active brush of the tile map.
1234    #[inline]
1235    pub fn active_brush(&self) -> Option<&TileMapBrushResource> {
1236        self.active_brush.as_ref()
1237    }
1238
1239    /// Sets new active brush of the tile map.
1240    #[inline]
1241    pub fn set_active_brush(&mut self, brush: Option<TileMapBrushResource>) {
1242        self.active_brush.set_value_and_mark_modified(brush);
1243    }
1244
1245    /// Calculates bounding rectangle in grid coordinates.
1246    #[inline]
1247    pub fn bounding_rect(&self) -> OptionTileRect {
1248        let Some(tiles) = self.tiles.as_ref().map(|r| r.data_ref()) else {
1249            return OptionTileRect::default();
1250        };
1251        let Some(tiles) = tiles.as_loaded_ref() else {
1252            return OptionTileRect::default();
1253        };
1254        tiles.bounding_rect()
1255    }
1256
1257    /// Calculates grid-space position (tile coordinates) from world-space. Could be used to find
1258    /// tile coordinates from arbitrary point in world space. It is especially useful, if the tile
1259    /// map is rotated or shifted.
1260    #[inline]
1261    pub fn world_to_grid(&self, world_position: Vector3<f32>) -> Vector2<i32> {
1262        let inv_global_transform = self.tile_map_transform().try_inverse().unwrap_or_default();
1263        let local_space_position = inv_global_transform.transform_point(&world_position.into());
1264        Vector2::new(
1265            local_space_position.x.floor() as i32,
1266            local_space_position.y.floor() as i32,
1267        )
1268    }
1269
1270    /// Calculates world-space position from grid-space position (tile coordinates).
1271    #[inline]
1272    pub fn grid_to_world(&self, grid_position: Vector2<i32>) -> Vector3<f32> {
1273        let v3 = grid_position.cast::<f32>().to_homogeneous();
1274        self.tile_map_transform().transform_point(&v3.into()).coords
1275    }
1276
1277    fn cells_touching_frustum(&self, frustum: &Frustum) -> OptionTileRect {
1278        let global_transform = self.global_transform();
1279
1280        fn make_ray(a: Vector3<f32>, b: Vector3<f32>) -> Ray {
1281            Ray {
1282                origin: a,
1283                dir: b - a,
1284            }
1285        }
1286
1287        let left_top_ray = make_ray(
1288            frustum.left_top_front_corner(),
1289            frustum.left_top_back_corner(),
1290        );
1291        let right_top_ray = make_ray(
1292            frustum.right_top_front_corner(),
1293            frustum.right_top_back_corner(),
1294        );
1295        let left_bottom_ray = make_ray(
1296            frustum.left_bottom_front_corner(),
1297            frustum.left_bottom_back_corner(),
1298        );
1299        let right_bottom_ray = make_ray(
1300            frustum.right_bottom_front_corner(),
1301            frustum.right_bottom_back_corner(),
1302        );
1303
1304        let plane =
1305            Plane::from_normal_and_point(&global_transform.look(), &global_transform.position())
1306                .unwrap_or_default();
1307
1308        let Some(left_top) = left_top_ray.plane_intersection_point(&plane) else {
1309            return None.into();
1310        };
1311        let Some(right_top) = right_top_ray.plane_intersection_point(&plane) else {
1312            return None.into();
1313        };
1314        let Some(left_bottom) = left_bottom_ray.plane_intersection_point(&plane) else {
1315            return None.into();
1316        };
1317        let Some(right_bottom) = right_bottom_ray.plane_intersection_point(&plane) else {
1318            return None.into();
1319        };
1320        let mut bounds = OptionTileRect::default();
1321        for corner in [left_top, right_top, left_bottom, right_bottom] {
1322            bounds.push(self.world_to_grid(corner))
1323        }
1324        bounds
1325    }
1326}
1327
1328impl Default for TileMap {
1329    fn default() -> Self {
1330        Self {
1331            base: Default::default(),
1332            tile_set: Default::default(),
1333            tiles: Default::default(),
1334            tile_scale: Vector2::repeat(1.0).into(),
1335            active_brush: Default::default(),
1336            hidden_tiles: Mutex::default(),
1337            before_effects: Vec::default(),
1338            after_effects: Vec::default(),
1339        }
1340    }
1341}
1342
1343impl Clone for TileMap {
1344    fn clone(&self) -> Self {
1345        Self {
1346            base: self.base.clone(),
1347            tile_set: self.tile_set.clone(),
1348            tiles: self.tiles.clone(),
1349            tile_scale: self.tile_scale.clone(),
1350            active_brush: self.active_brush.clone(),
1351            hidden_tiles: Mutex::default(),
1352            before_effects: self.before_effects.clone(),
1353            after_effects: self.after_effects.clone(),
1354        }
1355    }
1356}
1357
1358impl Deref for TileMap {
1359    type Target = Base;
1360
1361    fn deref(&self) -> &Self::Target {
1362        &self.base
1363    }
1364}
1365
1366impl DerefMut for TileMap {
1367    fn deref_mut(&mut self) -> &mut Self::Target {
1368        &mut self.base
1369    }
1370}
1371
1372impl ConstructorProvider<Node, Graph> for TileMap {
1373    fn constructor() -> NodeConstructor {
1374        NodeConstructor::new::<Self>()
1375            .with_variant("Tile Map", |_| {
1376                TileMapBuilder::new(BaseBuilder::new().with_name("Tile Map"))
1377                    .build_node()
1378                    .into()
1379            })
1380            .with_group("2D")
1381    }
1382}
1383
1384impl NodeTrait for TileMap {
1385    fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
1386        let Some(rect) = *self.bounding_rect() else {
1387            return AxisAlignedBoundingBox::default();
1388        };
1389
1390        let mut min_pos = rect.position.cast::<f32>().to_homogeneous();
1391        let mut max_pos = (rect.position + rect.size).cast::<f32>().to_homogeneous();
1392        min_pos.x *= -1.0;
1393        max_pos.x *= -1.0;
1394        let (min, max) = min_pos.inf_sup(&max_pos);
1395
1396        AxisAlignedBoundingBox::from_min_max(min, max)
1397    }
1398
1399    fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
1400        self.local_bounding_box()
1401            .transform(&self.global_transform())
1402    }
1403
1404    fn id(&self) -> Uuid {
1405        Self::type_uuid()
1406    }
1407
1408    fn collect_render_data(&self, ctx: &mut RenderContext) -> RdcControlFlow {
1409        if !self.should_be_rendered(ctx.frustum, ctx.render_mask) {
1410            return RdcControlFlow::Continue;
1411        }
1412
1413        if renderer::is_shadow_pass(ctx.render_pass_name) {
1414            return RdcControlFlow::Continue;
1415        }
1416
1417        let Some(ref tile_set_resource) = *self.tile_set else {
1418            return RdcControlFlow::Continue;
1419        };
1420
1421        let mut tile_set_lock = TileSetRef::new(tile_set_resource);
1422        let tile_set = tile_set_lock.as_loaded();
1423
1424        let mut hidden_tiles = self.hidden_tiles.safe_lock();
1425        hidden_tiles.clear();
1426
1427        let bounds = ctx
1428            .frustum
1429            .as_ref()
1430            .map(|f| self.cells_touching_frustum(f))
1431            .unwrap_or_default();
1432
1433        let mut tile_render_context = TileMapRenderContext {
1434            tile_map_handle: self.handle(),
1435            transform: self.tile_map_transform(),
1436            hidden_tiles: &mut hidden_tiles,
1437            context: ctx,
1438            bounds,
1439            tile_set,
1440        };
1441
1442        for effect in self.before_effects.iter() {
1443            effect
1444                .safe_lock()
1445                .render_special_tiles(&mut tile_render_context);
1446        }
1447        let bounds = tile_render_context.visible_bounds();
1448        let Some(tiles) = self.tiles.as_ref().map(|r| r.data_ref()) else {
1449            return RdcControlFlow::Continue;
1450        };
1451        let Some(tiles) = tiles.as_loaded_ref() else {
1452            return RdcControlFlow::Continue;
1453        };
1454        if bounds.is_some() {
1455            for (position, handle) in tiles.bounded_iter(bounds) {
1456                if bounds.contains(position) && tile_render_context.is_tile_visible(position) {
1457                    let handle = tile_render_context.get_animated_version(handle);
1458                    tile_render_context.draw_tile(position, handle);
1459                }
1460            }
1461        } else {
1462            for (position, handle) in tiles.iter() {
1463                if tile_render_context.is_tile_visible(position) {
1464                    let handle = tile_render_context.get_animated_version(handle);
1465                    tile_render_context.draw_tile(position, handle);
1466                }
1467            }
1468        }
1469        for effect in self.after_effects.iter() {
1470            effect
1471                .safe_lock()
1472                .render_special_tiles(&mut tile_render_context);
1473        }
1474        RdcControlFlow::Continue
1475    }
1476
1477    fn validate(&self, _scene: &Scene) -> Result<(), String> {
1478        if self.tile_set.is_none() {
1479            Err(
1480                "Tile set resource is not set. Tile map will not be rendered correctly!"
1481                    .to_string(),
1482            )
1483        } else {
1484            Ok(())
1485        }
1486    }
1487}
1488
1489/// Tile map builder allows you to create [`TileMap`] scene nodes.
1490pub struct TileMapBuilder {
1491    base_builder: BaseBuilder,
1492    tile_set: Option<TileSetResource>,
1493    tiles: TileMapData,
1494    tile_scale: Vector2<f32>,
1495    before_effects: Vec<TileMapEffectRef>,
1496    after_effects: Vec<TileMapEffectRef>,
1497}
1498
1499impl TileMapBuilder {
1500    /// Creates new tile map builder.
1501    pub fn new(base_builder: BaseBuilder) -> Self {
1502        Self {
1503            base_builder,
1504            tile_set: None,
1505            tiles: TileMapData::default(),
1506            tile_scale: Vector2::repeat(1.0),
1507            before_effects: Default::default(),
1508            after_effects: Default::default(),
1509        }
1510    }
1511
1512    /// Sets the desired tile set.
1513    pub fn with_tile_set(mut self, tile_set: TileSetResource) -> Self {
1514        self.tile_set = Some(tile_set);
1515        self
1516    }
1517
1518    /// Sets the actual tiles of the tile map.
1519    pub fn with_tiles(mut self, tiles: &Tiles) -> Self {
1520        for (pos, handle) in tiles.iter() {
1521            self.tiles.set(*pos, *handle);
1522        }
1523        self
1524    }
1525
1526    /// Sets the actual tile scaling.
1527    pub fn with_tile_scale(mut self, tile_scale: Vector2<f32>) -> Self {
1528        self.tile_scale = tile_scale;
1529        self
1530    }
1531
1532    /// Adds an effect to the tile map which will run before the tiles render.
1533    pub fn with_before_effect(mut self, effect: TileMapEffectRef) -> Self {
1534        self.before_effects.push(effect);
1535        self
1536    }
1537
1538    /// Adds an effect to the tile map which will run after the tiles render.
1539    pub fn with_after_effect(mut self, effect: TileMapEffectRef) -> Self {
1540        self.after_effects.push(effect);
1541        self
1542    }
1543
1544    /// Builds tile map scene node, but not adds it to a scene graph.
1545    pub fn build_node(self) -> Node {
1546        Node::new(TileMap {
1547            base: self.base_builder.build_base(),
1548            tile_set: self.tile_set.into(),
1549            tiles: Some(Resource::new_ok(
1550                Uuid::new_v4(),
1551                ResourceKind::Embedded,
1552                self.tiles,
1553            ))
1554            .into(),
1555            tile_scale: self.tile_scale.into(),
1556            active_brush: Default::default(),
1557            hidden_tiles: Mutex::default(),
1558            before_effects: self.before_effects,
1559            after_effects: self.after_effects,
1560        })
1561    }
1562
1563    /// Finishes tile map building and adds it to the specified scene graph.
1564    pub fn build(self, graph: &mut Graph) -> Handle<Node> {
1565        graph.add_node(self.build_node())
1566    }
1567}