1mod 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
94pub const VERSION: u8 = 0;
96
97pub 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
106pub struct TileMapRenderContext<'a, 'b> {
109 pub context: &'a mut RenderContext<'b>,
111 tile_map_handle: Handle<Node>,
113 transform: Matrix4<f32>,
115 bounds: OptionTileRect,
117 hidden_tiles: &'a mut FxHashSet<Vector2<i32>>,
118 tile_set: OptionTileSet<'a>,
119}
120
121impl TileMapRenderContext<'_, '_> {
122 pub fn transform(&self) -> &Matrix4<f32> {
124 &self.transform
125 }
126 pub fn tile_map_handle(&self) -> Handle<Node> {
128 self.tile_map_handle
129 }
130 pub fn position(&self) -> Vector3<f32> {
132 self.transform.position()
133 }
134 pub fn visible_bounds(&self) -> OptionTileRect {
136 self.bounds
137 }
138 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 pub fn is_tile_visible(&self, position: Vector2<i32>) -> bool {
155 !self.hidden_tiles.contains(&position)
156 }
157 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 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 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#[derive(Default, Debug, Copy, Clone)]
300pub struct ChangeFlag(bool);
301
302impl ChangeFlag {
303 #[inline]
305 pub fn needs_save(&self) -> bool {
306 self.0
307 }
308 #[inline]
310 pub fn reset(&mut self) {
311 self.0 = false;
312 }
313 #[inline]
315 pub fn set(&mut self) {
316 self.0 = true;
317 }
318}
319
320#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
322#[repr(C)] pub struct TileVertex {
324 pub position: Vector3<f32>,
326 pub tex_coord: Vector2<f32>,
328 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#[derive(Clone, Copy, Default, Debug, Visit, Reflect, PartialEq)]
368pub enum TilePaletteStage {
369 #[default]
371 Pages,
372 Tiles,
374}
375
376#[derive(Copy, Clone, Debug, Eq, PartialEq)]
378pub enum PageType {
379 Atlas,
382 Freeform,
384 Transform,
387 Animation,
393 Brush,
396}
397
398#[derive(Clone, Copy, Debug, PartialEq, Visit, Reflect)]
406pub enum ResourceTilePosition {
407 Page(Vector2<i32>),
409 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 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 pub fn is_page(&self) -> bool {
450 matches!(self, Self::Page(_))
451 }
452 pub fn is_tile(&self) -> bool {
454 matches!(self, Self::Tile(_, _))
455 }
456 pub fn stage(&self) -> TilePaletteStage {
458 match self {
459 Self::Page(_) => TilePaletteStage::Pages,
460 Self::Tile(_, _) => TilePaletteStage::Tiles,
461 }
462 }
463 pub fn stage_position(&self) -> Vector2<i32> {
466 match self {
467 Self::Page(p) => *p,
468 Self::Tile(_, p) => *p,
469 }
470 }
471 pub fn page(&self) -> Vector2<i32> {
473 match self {
474 Self::Page(p) => *p,
475 Self::Tile(p, _) => *p,
476 }
477 }
478 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#[derive(Clone, Reflect, Default, Debug, PartialEq, Visit, ComponentProvider, TypeUuidProvider)]
491#[type_uuid(id = "e429ca1b-a311-46c3-b580-d5a2f49db7e2")]
492pub struct Tile {
493 pub position: Vector2<i32>,
495 pub definition_handle: TileDefinitionHandle,
497}
498
499#[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)]
522pub enum TileBook {
526 #[default]
528 Empty,
529 TileSet(TileSetResource),
531 Brush(TileMapBrushResource),
533}
534
535impl TileBook {
536 #[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 #[inline]
547 pub fn is_tile_set(&self) -> bool {
548 matches!(self, TileBook::TileSet(_))
549 }
550 #[inline]
552 pub fn is_brush(&self) -> bool {
553 matches!(self, TileBook::Brush(_))
554 }
555 #[inline]
557 pub fn is_empty(&self) -> bool {
558 matches!(self, TileBook::Empty)
559 }
560 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 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 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 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 pub fn tile_set_ref(&self) -> Option<&TileSetResource> {
617 match self {
618 TileBook::TileSet(r) => Some(r),
619 _ => None,
620 }
621 }
622 pub fn brush_ref(&self) -> Option<&TileMapBrushResource> {
624 match self {
625 TileBook::Brush(r) => Some(r),
626 _ => None,
627 }
628 }
629 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 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 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 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 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 pub fn is_atlas_page(&self, position: Vector2<i32>) -> bool {
703 self.page_type(position) == Some(PageType::Atlas)
704 }
705 pub fn is_free_page(&self, position: Vector2<i32>) -> bool {
707 self.page_type(position) == Some(PageType::Freeform)
708 }
709 pub fn is_transform_page(&self, position: Vector2<i32>) -> bool {
711 self.page_type(position) == Some(PageType::Transform)
712 }
713 pub fn is_animation_page(&self, position: Vector2<i32>) -> bool {
715 self.page_type(position) == Some(PageType::Animation)
716 }
717 pub fn is_brush_page(&self, position: Vector2<i32>) -> bool {
719 self.page_type(position) == Some(PageType::Brush)
720 }
721 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 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 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 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 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 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 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 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 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 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 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#[derive(Clone, Default, Debug)]
894pub struct TileRenderData {
895 pub material_bounds: Option<TileMaterialBounds>,
897 pub color: Color,
899 empty: bool,
901}
902
903impl TileRenderData {
904 pub fn new(material_bounds: Option<TileMaterialBounds>, color: Color) -> Self {
906 Self {
907 material_bounds,
908 color,
909 empty: false,
910 }
911 }
912 pub fn empty() -> Self {
914 Self {
915 material_bounds: None,
916 color: Color::WHITE,
917 empty: true,
918 }
919 }
920 pub fn is_empty(&self) -> bool {
922 self.empty
923 }
924 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#[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 tile_set: InheritableVariable<Option<TileSetResource>>,
961 #[reflect(hidden)]
963 pub tiles: InheritableVariable<Option<TileMapDataResource>>,
964 tile_scale: InheritableVariable<Vector2<f32>>,
965 active_brush: InheritableVariable<Option<TileMapBrushResource>>,
966 #[reflect(hidden)]
969 hidden_tiles: Mutex<FxHashSet<Vector2<i32>>>,
970 #[reflect(hidden)]
976 pub before_effects: Vec<TileMapEffectRef>,
977 #[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
1003pub 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#[derive(Debug)]
1019pub enum TilePropertyError {
1020 MissingTileSet,
1022 TileSetNotLoaded,
1024 UnrecognizedName(ImmutableString),
1026 UnrecognizedUuid(Uuid),
1028 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 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 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 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 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 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 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 #[inline]
1165 pub fn tile_set(&self) -> Option<&TileSetResource> {
1166 self.tile_set.as_ref()
1167 }
1168
1169 #[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 #[inline]
1177 pub fn tiles(&self) -> Option<&TileMapDataResource> {
1178 self.tiles.as_ref()
1179 }
1180
1181 #[inline]
1183 pub fn set_tiles(&mut self, tiles: TileMapDataResource) {
1184 self.tiles.set_value_and_mark_modified(Some(tiles));
1185 }
1186
1187 #[inline]
1189 pub fn tile_scale(&self) -> Vector2<f32> {
1190 *self.tile_scale
1191 }
1192
1193 #[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 #[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 #[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 #[inline]
1226 pub fn active_brush(&self) -> Option<&TileMapBrushResource> {
1227 self.active_brush.as_ref()
1228 }
1229
1230 #[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 #[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 #[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 #[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
1480pub 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 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 pub fn with_tile_set(mut self, tile_set: TileSetResource) -> Self {
1505 self.tile_set = Some(tile_set);
1506 self
1507 }
1508
1509 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 pub fn with_tile_scale(mut self, tile_scale: Vector2<f32>) -> Self {
1519 self.tile_scale = tile_scale;
1520 self
1521 }
1522
1523 pub fn with_before_effect(mut self, effect: TileMapEffectRef) -> Self {
1525 self.before_effects.push(effect);
1526 self
1527 }
1528
1529 pub fn with_after_effect(mut self, effect: TileMapEffectRef) -> Self {
1531 self.after_effects.push(effect);
1532 self
1533 }
1534
1535 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 pub fn build(self, graph: &mut Graph) -> Handle<TileMap> {
1556 graph.add_node(self.build_node()).to_variant()
1557 }
1558}