1pub mod brush;
25mod data;
26mod effect;
27mod property;
28mod tile_collider;
29mod tile_rect;
30mod tile_source;
31pub mod tileset;
32mod transform;
33mod update;
34
35use brush::*;
36pub use data::*;
37pub use effect::*;
38use fxhash::FxHashSet;
39use fyrox_core::{
40 math::{frustum::Frustum, plane::Plane, ray::Ray},
41 parking_lot::Mutex,
42};
43use fyrox_resource::Resource;
44pub use tile_collider::*;
45pub use tile_rect::*;
46pub use tile_source::*;
47use tileset::*;
48pub use transform::*;
49pub use update::*;
50
51use crate::{
52 asset::{untyped::ResourceKind, ResourceDataRef},
53 core::{
54 algebra::{Matrix4, Vector2, Vector3},
55 color::Color,
56 math::{aabb::AxisAlignedBoundingBox, Matrix4Ext, TriangleDefinition},
57 pool::Handle,
58 reflect::prelude::*,
59 type_traits::prelude::*,
60 variable::InheritableVariable,
61 visitor::prelude::*,
62 ImmutableString,
63 },
64 graph::{constructor::ConstructorProvider, BaseSceneGraph},
65 material::{Material, MaterialResource, STANDARD_2D},
66 renderer::{self, bundle::RenderContext},
67 scene::{
68 base::{Base, BaseBuilder},
69 graph::Graph,
70 mesh::{
71 buffer::{
72 VertexAttributeDataType, VertexAttributeDescriptor, VertexAttributeUsage,
73 VertexTrait,
74 },
75 RenderPath,
76 },
77 node::{Node, NodeTrait, RdcControlFlow},
78 Scene,
79 },
80};
81use bytemuck::{Pod, Zeroable};
82use std::{
83 error::Error,
84 fmt::Display,
85 ops::{Deref, DerefMut},
86 path::PathBuf,
87};
88
89use super::{dim2::rectangle::RectangleVertex, node::constructor::NodeConstructor};
90
91use crate::lazy_static::*;
92
93pub const VERSION: u8 = 1;
95
96lazy_static! {
97 pub static ref DEFAULT_TILE_MATERIAL: MaterialResource = MaterialResource::new_ok(
99 ResourceKind::External("__DefaultTileMaterial".into()),
100 Material::standard_tile()
101 );
102}
103
104pub struct TileMapRenderContext<'a, 'b> {
107 pub context: &'a mut RenderContext<'b>,
109 tile_map_handle: Handle<Node>,
111 transform: Matrix4<f32>,
113 bounds: OptionTileRect,
115 hidden_tiles: &'a mut FxHashSet<Vector2<i32>>,
116 tile_set: OptionTileSet<'a>,
117}
118
119impl TileMapRenderContext<'_, '_> {
120 pub fn transform(&self) -> &Matrix4<f32> {
122 &self.transform
123 }
124 pub fn tile_map_handle(&self) -> Handle<Node> {
126 self.tile_map_handle
127 }
128 pub fn position(&self) -> Vector3<f32> {
130 self.transform.position()
131 }
132 pub fn visible_bounds(&self) -> OptionTileRect {
134 self.bounds
135 }
136 pub fn set_tile_visible(&mut self, position: Vector2<i32>, is_visible: bool) {
142 if is_visible {
143 let _ = self.hidden_tiles.remove(&position);
144 } else {
145 let _ = self.hidden_tiles.insert(position);
146 }
147 }
148 pub fn is_tile_visible(&self, position: Vector2<i32>) -> bool {
153 !self.hidden_tiles.contains(&position)
154 }
155 pub fn get_animated_version(&self, handle: TileDefinitionHandle) -> TileDefinitionHandle {
158 self.tile_set
159 .get_animated_version(self.context.elapsed_time, handle)
160 .unwrap_or(handle)
161 }
162 pub fn draw_tile(&mut self, position: Vector2<i32>, handle: TileDefinitionHandle) {
168 let Some(data) = self.tile_set.get_tile_render_data(handle.into()) else {
169 return;
170 };
171 self.push_tile(position, &data);
172 }
173
174 pub fn push_tile(&mut self, position: Vector2<i32>, data: &TileRenderData) {
177 let color = data.color;
178 if let Some(tile_bounds) = data.material_bounds.as_ref() {
179 let material = &tile_bounds.material;
180 let bounds = &tile_bounds.bounds;
181 self.push_material_tile(position, material, bounds, color);
182 } else {
183 self.push_color_tile(position, color);
184 }
185 }
186
187 fn push_color_tile(&mut self, position: Vector2<i32>, color: Color) {
188 let position = position.cast::<f32>();
189 let vertices = [(0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]
190 .map(|(x, y)| Vector2::new(x, y))
191 .map(|p| make_rect_vertex(&self.transform, position + p, color));
192
193 let triangles = [[0, 1, 2], [2, 3, 0]].map(TriangleDefinition);
194
195 let sort_index = self.context.calculate_sorting_index(self.position());
196
197 self.context.storage.push_triangles(
198 RectangleVertex::layout(),
199 &STANDARD_2D.resource,
200 RenderPath::Forward,
201 sort_index,
202 self.tile_map_handle,
203 &mut move |mut vertex_buffer, mut triangle_buffer| {
204 let start_vertex_index = vertex_buffer.vertex_count();
205
206 vertex_buffer.push_vertices(&vertices).unwrap();
207
208 triangle_buffer
209 .push_triangles_iter_with_offset(start_vertex_index, triangles.into_iter());
210 },
211 );
212 }
213
214 fn push_material_tile(
215 &mut self,
216 position: Vector2<i32>,
217 material: &MaterialResource,
218 bounds: &TileBounds,
219 color: Color,
220 ) {
221 let position = position.cast::<f32>();
222 let uvs = [
223 bounds.right_top_corner,
224 bounds.left_top_corner,
225 bounds.left_bottom_corner,
226 bounds.right_bottom_corner,
227 ];
228 let vertices = [
229 (1.0, 1.0, uvs[0]),
230 (0.0, 1.0, uvs[1]),
231 (0.0, 0.0, uvs[2]),
232 (1.0, 0.0, uvs[3]),
233 ]
234 .map(|(x, y, uv)| (Vector2::new(x, y), uv))
235 .map(|(p, uv)| make_tile_vertex(&self.transform, position + p, uv, color));
236
237 let triangles = [[0, 1, 2], [2, 3, 0]].map(TriangleDefinition);
238
239 let sort_index = self.context.calculate_sorting_index(self.position());
240
241 self.context.storage.push_triangles(
242 TileVertex::layout(),
243 material,
244 RenderPath::Forward,
245 sort_index,
246 self.tile_map_handle,
247 &mut move |mut vertex_buffer, mut triangle_buffer| {
248 let start_vertex_index = vertex_buffer.vertex_count();
249
250 vertex_buffer.push_vertices(&vertices).unwrap();
251
252 triangle_buffer
253 .push_triangles_iter_with_offset(start_vertex_index, triangles.into_iter());
254 },
255 );
256 }
257}
258
259fn make_rect_vertex(
260 transform: &Matrix4<f32>,
261 position: Vector2<f32>,
262 color: Color,
263) -> RectangleVertex {
264 RectangleVertex {
265 position: transform
266 .transform_point(&position.to_homogeneous().into())
267 .coords,
268 tex_coord: Vector2::default(),
269 color,
270 }
271}
272
273fn make_tile_vertex(
274 transform: &Matrix4<f32>,
275 position: Vector2<f32>,
276 tex_coord: Vector2<u32>,
277 color: Color,
278) -> TileVertex {
279 TileVertex {
280 position: transform
281 .transform_point(&position.to_homogeneous().into())
282 .coords,
283 tex_coord: tex_coord.cast::<f32>(),
284 color,
285 }
286}
287
288#[derive(Default, Debug, Copy, Clone)]
290pub struct ChangeFlag(bool);
291
292impl ChangeFlag {
293 #[inline]
295 pub fn needs_save(&self) -> bool {
296 self.0
297 }
298 #[inline]
300 pub fn reset(&mut self) {
301 self.0 = false;
302 }
303 #[inline]
305 pub fn set(&mut self) {
306 self.0 = true;
307 }
308}
309
310#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
312#[repr(C)] pub struct TileVertex {
314 pub position: Vector3<f32>,
316 pub tex_coord: Vector2<f32>,
318 pub color: Color,
320}
321
322impl VertexTrait for TileVertex {
323 fn layout() -> &'static [VertexAttributeDescriptor] {
324 &[
325 VertexAttributeDescriptor {
326 usage: VertexAttributeUsage::Position,
327 data_type: VertexAttributeDataType::F32,
328 size: 3,
329 divisor: 0,
330 shader_location: 0,
331 normalized: false,
332 },
333 VertexAttributeDescriptor {
334 usage: VertexAttributeUsage::TexCoord0,
335 data_type: VertexAttributeDataType::F32,
336 size: 2,
337 divisor: 0,
338 shader_location: 1,
339 normalized: false,
340 },
341 VertexAttributeDescriptor {
342 usage: VertexAttributeUsage::Color,
343 data_type: VertexAttributeDataType::U8,
344 size: 4,
345 divisor: 0,
346 shader_location: 2,
347 normalized: true,
348 },
349 ]
350 }
351}
352
353#[derive(Clone, Copy, Default, Debug, Visit, Reflect, PartialEq)]
358pub enum TilePaletteStage {
359 #[default]
361 Pages,
362 Tiles,
364}
365
366#[derive(Copy, Clone, Debug, Eq, PartialEq)]
368pub enum PageType {
369 Atlas,
372 Freeform,
374 Transform,
377 Animation,
383 Brush,
386}
387
388#[derive(Clone, Copy, Debug, PartialEq)]
396pub enum ResourceTilePosition {
397 Page(Vector2<i32>),
399 Tile(Vector2<i32>, Vector2<i32>),
402}
403
404impl From<TileDefinitionHandle> for ResourceTilePosition {
405 fn from(value: TileDefinitionHandle) -> Self {
406 Self::Tile(value.page(), value.tile())
407 }
408}
409
410impl ResourceTilePosition {
411 pub fn new(stage: TilePaletteStage, page: Vector2<i32>, tile: Vector2<i32>) -> Self {
416 match stage {
417 TilePaletteStage::Pages => Self::Page(tile),
418 TilePaletteStage::Tiles => Self::Tile(page, tile),
419 }
420 }
421 pub fn is_page(&self) -> bool {
423 matches!(self, Self::Page(_))
424 }
425 pub fn is_tile(&self) -> bool {
427 matches!(self, Self::Tile(_, _))
428 }
429 pub fn stage(&self) -> TilePaletteStage {
431 match self {
432 Self::Page(_) => TilePaletteStage::Pages,
433 Self::Tile(_, _) => TilePaletteStage::Tiles,
434 }
435 }
436 pub fn stage_position(&self) -> Vector2<i32> {
439 match self {
440 Self::Page(p) => *p,
441 Self::Tile(_, p) => *p,
442 }
443 }
444 pub fn page(&self) -> Vector2<i32> {
446 match self {
447 Self::Page(p) => *p,
448 Self::Tile(p, _) => *p,
449 }
450 }
451 pub fn handle(&self) -> Option<TileDefinitionHandle> {
453 if let Self::Tile(p, t) = self {
454 TileDefinitionHandle::try_new(*p, *t)
455 } else {
456 None
457 }
458 }
459}
460
461#[derive(Clone, Reflect, Default, Debug, PartialEq, Visit, ComponentProvider, TypeUuidProvider)]
464#[type_uuid(id = "e429ca1b-a311-46c3-b580-d5a2f49db7e2")]
465pub struct Tile {
466 pub position: Vector2<i32>,
468 pub definition_handle: TileDefinitionHandle,
470}
471
472#[derive(Debug, Clone)]
474pub struct TileIter<I> {
475 source: TileBook,
476 stage: TilePaletteStage,
477 page: Vector2<i32>,
478 positions: I,
479}
480
481impl<I: Iterator<Item = Vector2<i32>>> Iterator for TileIter<I> {
482 type Item = (Vector2<i32>, TileDefinitionHandle);
483
484 fn next(&mut self) -> Option<Self::Item> {
485 self.positions.find_map(|p| {
486 let h = self
487 .source
488 .get_tile_handle(ResourceTilePosition::new(self.stage, self.page, p))?;
489 Some((p, h))
490 })
491 }
492}
493
494#[derive(Debug, Default, Clone, PartialEq, Visit, Reflect)]
495pub enum TileBook {
499 #[default]
501 Empty,
502 TileSet(TileSetResource),
504 Brush(TileMapBrushResource),
506}
507
508impl TileBook {
509 #[inline]
511 pub fn page_icon(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
512 match self {
513 TileBook::Empty => None,
514 TileBook::TileSet(r) => r.state().data()?.page_icon(position),
515 TileBook::Brush(r) => r.state().data()?.page_icon(position),
516 }
517 }
518 #[inline]
520 pub fn is_tile_set(&self) -> bool {
521 matches!(self, TileBook::TileSet(_))
522 }
523 #[inline]
525 pub fn is_brush(&self) -> bool {
526 matches!(self, TileBook::Brush(_))
527 }
528 #[inline]
530 pub fn is_empty(&self) -> bool {
531 matches!(self, TileBook::Empty)
532 }
533 pub fn name(&self) -> String {
535 self.path()
536 .map(|x| x.to_string_lossy().into_owned())
537 .unwrap_or_else(|| "Error".into())
538 }
539 pub fn path(&self) -> Option<PathBuf> {
541 match self {
542 TileBook::Empty => None,
543 TileBook::TileSet(r) => r.kind().into_path(),
544 TileBook::Brush(r) => r.kind().into_path(),
545 }
546 }
547 pub fn needs_save(&self) -> bool {
549 match self {
550 TileBook::Empty => false,
551 TileBook::TileSet(r) => {
552 r.header().kind.is_external() && r.data_ref().change_count.needs_save()
553 }
554 TileBook::Brush(r) => {
555 r.header().kind.is_external() && r.data_ref().change_count.needs_save()
556 }
557 }
558 }
559 pub fn save(&self) -> Result<(), Box<dyn Error>> {
562 match self {
563 TileBook::Empty => Ok(()),
564 TileBook::TileSet(r) => {
565 if r.header().kind.is_external() && r.data_ref().change_count.needs_save() {
566 let result = r.save_back();
567 if result.is_ok() {
568 r.data_ref().change_count.reset();
569 }
570 result
571 } else {
572 Ok(())
573 }
574 }
575 TileBook::Brush(r) => {
576 if r.header().kind.is_external() && r.data_ref().change_count.needs_save() {
577 let result = r.save_back();
578 if result.is_ok() {
579 r.data_ref().change_count.reset();
580 }
581 result
582 } else {
583 Ok(())
584 }
585 }
586 }
587 }
588 pub fn tile_set_ref(&self) -> Option<&TileSetResource> {
590 match self {
591 TileBook::TileSet(r) => Some(r),
592 _ => None,
593 }
594 }
595 pub fn get_tile_set(&self) -> Option<TileSetResource> {
599 match self {
600 TileBook::Empty => None,
601 TileBook::TileSet(r) => Some(r.clone()),
602 TileBook::Brush(r) => r.state().data()?.tile_set.clone(),
603 }
604 }
605 pub fn get_all_tile_positions(&self, page: Vector2<i32>) -> Vec<Vector2<i32>> {
607 match self {
608 TileBook::Empty => Vec::new(),
609 TileBook::TileSet(r) => r
610 .state()
611 .data()
612 .map(|r| r.keys_on_page(page))
613 .unwrap_or_default(),
614 TileBook::Brush(r) => r
615 .state()
616 .data()
617 .and_then(|r| {
618 r.pages
619 .get(&page)
620 .map(|p| p.tiles.keys().copied().collect())
621 })
622 .unwrap_or_default(),
623 }
624 }
625 pub fn get_all_page_positions(&self) -> Vec<Vector2<i32>> {
627 match self {
628 TileBook::Empty => Vec::new(),
629 TileBook::TileSet(r) => r.state().data().map(|r| r.page_keys()).unwrap_or_default(),
630 TileBook::Brush(r) => r
631 .state()
632 .data()
633 .map(|r| r.pages.keys().copied().collect())
634 .unwrap_or_default(),
635 }
636 }
637 pub fn has_page_at(&self, position: Vector2<i32>) -> bool {
639 match self {
640 TileBook::Empty => false,
641 TileBook::TileSet(r) => r
642 .state()
643 .data()
644 .map(|r| r.pages.contains_key(&position))
645 .unwrap_or(false),
646 TileBook::Brush(r) => r
647 .state()
648 .data()
649 .map(|r| r.pages.contains_key(&position))
650 .unwrap_or(false),
651 }
652 }
653 pub fn page_type(&self, position: Vector2<i32>) -> Option<PageType> {
655 match self {
656 TileBook::Empty => None,
657 TileBook::TileSet(r) => r.state().data()?.get_page(position).map(|p| p.page_type()),
658 TileBook::Brush(r) => {
659 if r.state().data()?.has_page_at(position) {
660 Some(PageType::Brush)
661 } else {
662 None
663 }
664 }
665 }
666 }
667 pub fn is_atlas_page(&self, position: Vector2<i32>) -> bool {
669 self.page_type(position) == Some(PageType::Atlas)
670 }
671 pub fn is_free_page(&self, position: Vector2<i32>) -> bool {
673 self.page_type(position) == Some(PageType::Freeform)
674 }
675 pub fn is_transform_page(&self, position: Vector2<i32>) -> bool {
677 self.page_type(position) == Some(PageType::Transform)
678 }
679 pub fn is_animation_page(&self, position: Vector2<i32>) -> bool {
681 self.page_type(position) == Some(PageType::Animation)
682 }
683 pub fn is_brush_page(&self, position: Vector2<i32>) -> bool {
685 self.page_type(position) == Some(PageType::Brush)
686 }
687 pub fn has_tile_at(&self, page: Vector2<i32>, tile: Vector2<i32>) -> bool {
689 match self {
690 TileBook::Empty => false,
691 TileBook::TileSet(r) => r
692 .state()
693 .data()
694 .map(|r| r.has_tile_at(page, tile))
695 .unwrap_or(false),
696 TileBook::Brush(r) => r
697 .state()
698 .data()
699 .map(|r| r.has_tile_at(page, tile))
700 .unwrap_or(false),
701 }
702 }
703 pub fn get_tile_handle(&self, position: ResourceTilePosition) -> Option<TileDefinitionHandle> {
707 match self {
708 TileBook::Empty => None,
709 TileBook::TileSet(r) => r.state().data()?.redirect_handle(position),
710 TileBook::Brush(r) => r.state().data()?.redirect_handle(position),
711 }
712 }
713 pub fn get_tile_iter<I: Iterator<Item = Vector2<i32>>>(
717 &self,
718 stage: TilePaletteStage,
719 page: Vector2<i32>,
720 positions: I,
721 ) -> TileIter<I> {
722 TileIter {
723 source: self.clone(),
724 stage,
725 page,
726 positions,
727 }
728 }
729 pub fn get_tiles<I: Iterator<Item = Vector2<i32>>>(
732 &self,
733 stage: TilePaletteStage,
734 page: Vector2<i32>,
735 iter: I,
736 tiles: &mut Tiles,
737 ) {
738 match self {
739 TileBook::Empty => (),
740 TileBook::TileSet(res) => {
741 if let Some(tile_set) = res.state().data() {
742 tile_set.get_tiles(stage, page, iter, tiles);
743 }
744 }
745 TileBook::Brush(res) => {
746 if let Some(brush) = res.state().data() {
747 brush.get_tiles(stage, page, iter, tiles);
748 }
749 }
750 }
751 }
752
753 pub fn is_missing_tile_set(&self) -> bool {
755 match self {
756 TileBook::Empty => false,
757 TileBook::TileSet(_) => false,
758 TileBook::Brush(resource) => resource
759 .state()
760 .data()
761 .map(|b| b.is_missing_tile_set())
762 .unwrap_or(false),
763 }
764 }
765
766 pub fn get_tile_render_data(&self, position: ResourceTilePosition) -> Option<TileRenderData> {
776 match self {
777 TileBook::Empty => None,
778 TileBook::TileSet(resource) => resource.state().data()?.get_tile_render_data(position),
779 TileBook::Brush(resource) => resource.state().data()?.get_tile_render_data(position),
780 }
781 }
782
783 pub fn tile_render_loop<F>(&self, stage: TilePaletteStage, page: Vector2<i32>, func: F)
787 where
788 F: FnMut(Vector2<i32>, TileRenderData),
789 {
790 match self {
791 TileBook::Empty => (),
792 TileBook::TileSet(res) => {
793 if let Some(data) = res.state().data() {
794 data.palette_render_loop(stage, page, func)
795 }
796 }
797 TileBook::Brush(res) => {
798 if let Some(data) = res.state().data() {
799 data.palette_render_loop(stage, page, func)
800 }
801 }
802 };
803 }
804 pub fn tile_collider_loop<F>(&self, page: Vector2<i32>, func: F)
807 where
808 F: FnMut(Vector2<i32>, Uuid, Color, &TileCollider),
809 {
810 match self {
811 TileBook::Empty => (),
812 TileBook::TileSet(res) => {
813 if let Some(data) = res.state().data() {
814 data.tile_collider_loop(page, func)
815 }
816 }
817 TileBook::Brush(_) => (),
818 };
819 }
820 pub fn get_tile_bounds(&self, position: ResourceTilePosition) -> Option<TileMaterialBounds> {
823 match self {
824 TileBook::Empty => None,
825 TileBook::TileSet(res) => res
826 .state()
827 .data()
828 .map(|d| d.get_tile_bounds(position))
829 .unwrap_or_default(),
830 TileBook::Brush(res) => res
831 .state()
832 .data()
833 .map(|d| d.get_tile_bounds(position))
834 .unwrap_or_default(),
835 }
836 }
837 pub fn tiles_bounds(&self, stage: TilePaletteStage, page: Vector2<i32>) -> OptionTileRect {
839 match self {
840 TileBook::Empty => OptionTileRect::default(),
841 TileBook::TileSet(res) => res.data_ref().tiles_bounds(stage, page),
842 TileBook::Brush(res) => res.data_ref().tiles_bounds(stage, page),
843 }
844 }
845}
846
847#[derive(Clone, Default, Debug)]
849pub struct TileRenderData {
850 pub material_bounds: Option<TileMaterialBounds>,
852 pub color: Color,
854}
855
856impl TileRenderData {
857 pub fn missing_data() -> TileRenderData {
859 Self {
860 material_bounds: None,
861 color: Color::HOT_PINK,
862 }
863 }
864}
865
866impl OrthoTransform for TileRenderData {
867 fn x_flipped(mut self) -> Self {
868 self.material_bounds = self.material_bounds.map(|b| b.x_flipped());
869 self
870 }
871
872 fn rotated(mut self, amount: i8) -> Self {
873 self.material_bounds = self.material_bounds.map(|b| b.rotated(amount));
874 self
875 }
876}
877
878#[derive(Reflect, Debug, ComponentProvider, TypeUuidProvider)]
887#[type_uuid(id = "aa9a3385-a4af-4faf-a69a-8d3af1a3aa67")]
888pub struct TileMap {
889 base: Base,
890 tile_set: InheritableVariable<Option<TileSetResource>>,
892 #[reflect(hidden)]
894 pub tiles: InheritableVariable<Option<TileMapDataResource>>,
895 tile_scale: InheritableVariable<Vector2<f32>>,
896 active_brush: InheritableVariable<Option<TileMapBrushResource>>,
897 #[reflect(hidden)]
900 hidden_tiles: Mutex<FxHashSet<Vector2<i32>>>,
901 #[reflect(hidden)]
907 pub before_effects: Vec<TileMapEffectRef>,
908 #[reflect(hidden)]
912 pub after_effects: Vec<TileMapEffectRef>,
913}
914
915impl Visit for TileMap {
916 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
917 let mut region = visitor.enter_region(name)?;
918 let mut version = if region.is_reading() { 0 } else { VERSION };
919 let _ = version.visit("Version", &mut region);
920 self.base.visit("Base", &mut region)?;
921 self.tile_set.visit("TileSet", &mut region)?;
922 self.tile_scale.visit("TileScale", &mut region)?;
923 self.active_brush.visit("ActiveBrush", &mut region)?;
924 match version {
925 0 => {
926 let mut tiles = InheritableVariable::new_non_modified(Tiles::default());
927 let result = tiles.visit("Tiles", &mut region);
928 result?;
929 let mut data = TileMapData::default();
930 for (p, h) in tiles.iter() {
931 data.set(*p, *h);
932 }
933 self.tiles = Some(Resource::new_ok(ResourceKind::Embedded, data)).into();
934 }
935 VERSION => {
936 self.tiles.visit("Tiles", &mut region)?;
937 }
938 _ => return Err(VisitError::User("Unknown version".into())),
939 }
940 Ok(())
941 }
942}
943
944pub struct TileMapDataRef<'a> {
946 tile_set: ResourceDataRef<'a, TileSet>,
947 handle: TileDefinitionHandle,
948}
949
950impl Deref for TileMapDataRef<'_> {
951 type Target = TileData;
952
953 fn deref(&self) -> &Self::Target {
954 self.tile_set.tile_data(self.handle).unwrap()
955 }
956}
957
958#[derive(Debug)]
960pub enum TilePropertyError {
961 MissingTileSet,
963 TileSetNotLoaded,
965 UnrecognizedName(ImmutableString),
967 UnrecognizedUuid(Uuid),
969 WrongType(&'static str),
971}
972
973impl Display for TilePropertyError {
974 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
975 match self {
976 TilePropertyError::MissingTileSet => write!(f, "The tile map has no tile set."),
977 TilePropertyError::TileSetNotLoaded => {
978 write!(f, "The tile map's tile set is not loaded.")
979 }
980 TilePropertyError::UnrecognizedName(name) => {
981 write!(f, "There is no property with this name: {name}")
982 }
983 TilePropertyError::UnrecognizedUuid(uuid) => {
984 write!(f, "There is no property with this UUID: {uuid}")
985 }
986 TilePropertyError::WrongType(message) => write!(f, "Property type error: {message}"),
987 }
988 }
989}
990
991impl Error for TilePropertyError {}
992
993impl TileMap {
994 pub fn tile_handle(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
996 let tiles = self.tiles.as_ref()?.data_ref();
997 tiles.as_loaded_ref()?.get(position)
998 }
999 pub fn tile_data(&self, position: Vector2<i32>) -> Option<TileMapDataRef> {
1002 let handle = self.tile_handle(position)?;
1003 let tile_set = self.tile_set.as_ref()?.data_ref();
1004 if tile_set.as_loaded_ref()?.tile_data(handle).is_some() {
1005 Some(TileMapDataRef { tile_set, handle })
1006 } else {
1007 None
1008 }
1009 }
1010 pub fn tile_property_value<T>(
1016 &self,
1017 position: Vector2<i32>,
1018 property_id: Uuid,
1019 ) -> Result<T, TilePropertyError>
1020 where
1021 T: TryFrom<TileSetPropertyValue, Error = TilePropertyError> + Default,
1022 {
1023 let Some(handle) = self.tile_handle(position) else {
1024 return Ok(T::default());
1025 };
1026 let tile_set = self
1027 .tile_set
1028 .as_ref()
1029 .ok_or(TilePropertyError::MissingTileSet)?
1030 .data_ref();
1031 let tile_set = tile_set
1032 .as_loaded_ref()
1033 .ok_or(TilePropertyError::TileSetNotLoaded)?;
1034 tile_set
1035 .property_value(handle, property_id)
1036 .map(T::try_from)
1037 .unwrap_or_else(|| Ok(T::default()))
1038 }
1039 pub fn tile_property_value_by_name(
1045 &self,
1046 position: Vector2<i32>,
1047 property_name: &ImmutableString,
1048 ) -> Result<TileSetPropertyValue, TilePropertyError> {
1049 let tile_set = self
1050 .tile_set
1051 .as_ref()
1052 .ok_or(TilePropertyError::MissingTileSet)?
1053 .data_ref();
1054 let tile_set = tile_set
1055 .as_loaded_ref()
1056 .ok_or(TilePropertyError::TileSetNotLoaded)?;
1057 let property = tile_set
1058 .find_property_by_name(property_name)
1059 .ok_or_else(|| TilePropertyError::UnrecognizedName(property_name.clone()))?;
1060 let Some(handle) = self.tile_handle(position) else {
1061 return Ok(property.prop_type.default_value());
1062 };
1063 Ok(tile_set
1064 .property_value(handle, property.uuid)
1065 .unwrap_or_else(|| property.prop_type.default_value()))
1066 }
1067 pub fn tile_property_value_by_uuid_untyped(
1073 &self,
1074 position: Vector2<i32>,
1075 property_id: Uuid,
1076 ) -> Result<TileSetPropertyValue, TilePropertyError> {
1077 let tile_set = self
1078 .tile_set
1079 .as_ref()
1080 .ok_or(TilePropertyError::MissingTileSet)?
1081 .data_ref();
1082 let tile_set = tile_set
1083 .as_loaded_ref()
1084 .ok_or(TilePropertyError::TileSetNotLoaded)?;
1085 let value = if let Some(handle) = self.tile_handle(position) {
1086 tile_set
1087 .tile_data(handle)
1088 .and_then(|d| d.properties.get(&property_id))
1089 .cloned()
1090 } else {
1091 None
1092 };
1093 if let Some(value) = value {
1094 Ok(value)
1095 } else {
1096 let property = tile_set
1097 .find_property(property_id)
1098 .ok_or(TilePropertyError::UnrecognizedUuid(property_id))?;
1099 Ok(property.prop_type.default_value())
1100 }
1101 }
1102 pub fn tile_map_transform(&self) -> Matrix4<f32> {
1104 self.global_transform()
1105 .prepend_nonuniform_scaling(&Vector3::new(-1.0, 1.0, 1.0))
1106 }
1107 #[inline]
1109 pub fn tile_set(&self) -> Option<&TileSetResource> {
1110 self.tile_set.as_ref()
1111 }
1112
1113 #[inline]
1115 pub fn set_tile_set(&mut self, tile_set: Option<TileSetResource>) {
1116 self.tile_set.set_value_and_mark_modified(tile_set);
1117 }
1118
1119 #[inline]
1121 pub fn tiles(&self) -> Option<&TileMapDataResource> {
1122 self.tiles.as_ref()
1123 }
1124
1125 #[inline]
1127 pub fn set_tiles(&mut self, tiles: TileMapDataResource) {
1128 self.tiles.set_value_and_mark_modified(Some(tiles));
1129 }
1130
1131 #[inline]
1133 pub fn tile_scale(&self) -> Vector2<f32> {
1134 *self.tile_scale
1135 }
1136
1137 #[inline]
1139 pub fn set_tile_scale(&mut self, tile_scale: Vector2<f32>) {
1140 self.tile_scale.set_value_and_mark_modified(tile_scale);
1141 }
1142
1143 #[inline]
1146 pub fn insert_tile(
1147 &mut self,
1148 position: Vector2<i32>,
1149 tile: TileDefinitionHandle,
1150 ) -> Option<TileDefinitionHandle> {
1151 self.tiles
1152 .as_ref()?
1153 .data_ref()
1154 .as_loaded_mut()?
1155 .replace(position, Some(tile))
1156 }
1157
1158 #[inline]
1160 pub fn remove_tile(&mut self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
1161 self.tiles
1162 .as_ref()?
1163 .data_ref()
1164 .as_loaded_mut()?
1165 .replace(position, None)
1166 }
1167
1168 #[inline]
1170 pub fn active_brush(&self) -> Option<&TileMapBrushResource> {
1171 self.active_brush.as_ref()
1172 }
1173
1174 #[inline]
1176 pub fn set_active_brush(&mut self, brush: Option<TileMapBrushResource>) {
1177 self.active_brush.set_value_and_mark_modified(brush);
1178 }
1179
1180 #[inline]
1182 pub fn bounding_rect(&self) -> OptionTileRect {
1183 let Some(tiles) = self.tiles.as_ref().map(|r| r.data_ref()) else {
1184 return OptionTileRect::default();
1185 };
1186 let Some(tiles) = tiles.as_loaded_ref() else {
1187 return OptionTileRect::default();
1188 };
1189 tiles.bounding_rect()
1190 }
1191
1192 #[inline]
1196 pub fn world_to_grid(&self, world_position: Vector3<f32>) -> Vector2<i32> {
1197 let inv_global_transform = self.tile_map_transform().try_inverse().unwrap_or_default();
1198 let local_space_position = inv_global_transform.transform_point(&world_position.into());
1199 Vector2::new(
1200 local_space_position.x.floor() as i32,
1201 local_space_position.y.floor() as i32,
1202 )
1203 }
1204
1205 #[inline]
1207 pub fn grid_to_world(&self, grid_position: Vector2<i32>) -> Vector3<f32> {
1208 let v3 = grid_position.cast::<f32>().to_homogeneous();
1209 self.tile_map_transform().transform_point(&v3.into()).coords
1210 }
1211
1212 fn cells_touching_frustum(&self, frustum: &Frustum) -> OptionTileRect {
1213 let global_transform = self.global_transform();
1214
1215 fn make_ray(a: Vector3<f32>, b: Vector3<f32>) -> Ray {
1216 Ray {
1217 origin: a,
1218 dir: b - a,
1219 }
1220 }
1221
1222 let left_top_ray = make_ray(
1223 frustum.left_top_front_corner(),
1224 frustum.left_top_back_corner(),
1225 );
1226 let right_top_ray = make_ray(
1227 frustum.right_top_front_corner(),
1228 frustum.right_top_back_corner(),
1229 );
1230 let left_bottom_ray = make_ray(
1231 frustum.left_bottom_front_corner(),
1232 frustum.left_bottom_back_corner(),
1233 );
1234 let right_bottom_ray = make_ray(
1235 frustum.right_bottom_front_corner(),
1236 frustum.right_bottom_back_corner(),
1237 );
1238
1239 let plane =
1240 Plane::from_normal_and_point(&global_transform.look(), &global_transform.position())
1241 .unwrap_or_default();
1242
1243 let Some(left_top) = left_top_ray.plane_intersection_point(&plane) else {
1244 return None.into();
1245 };
1246 let Some(right_top) = right_top_ray.plane_intersection_point(&plane) else {
1247 return None.into();
1248 };
1249 let Some(left_bottom) = left_bottom_ray.plane_intersection_point(&plane) else {
1250 return None.into();
1251 };
1252 let Some(right_bottom) = right_bottom_ray.plane_intersection_point(&plane) else {
1253 return None.into();
1254 };
1255 let mut bounds = OptionTileRect::default();
1256 for corner in [left_top, right_top, left_bottom, right_bottom] {
1257 bounds.push(self.world_to_grid(corner))
1258 }
1259 bounds
1260 }
1261}
1262
1263impl Default for TileMap {
1264 fn default() -> Self {
1265 Self {
1266 base: Default::default(),
1267 tile_set: Default::default(),
1268 tiles: Default::default(),
1269 tile_scale: Vector2::repeat(1.0).into(),
1270 active_brush: Default::default(),
1271 hidden_tiles: Mutex::default(),
1272 before_effects: Vec::default(),
1273 after_effects: Vec::default(),
1274 }
1275 }
1276}
1277
1278impl Clone for TileMap {
1279 fn clone(&self) -> Self {
1280 Self {
1281 base: self.base.clone(),
1282 tile_set: self.tile_set.clone(),
1283 tiles: self.tiles.clone(),
1284 tile_scale: self.tile_scale.clone(),
1285 active_brush: self.active_brush.clone(),
1286 hidden_tiles: Mutex::default(),
1287 before_effects: self.before_effects.clone(),
1288 after_effects: self.after_effects.clone(),
1289 }
1290 }
1291}
1292
1293impl Deref for TileMap {
1294 type Target = Base;
1295
1296 fn deref(&self) -> &Self::Target {
1297 &self.base
1298 }
1299}
1300
1301impl DerefMut for TileMap {
1302 fn deref_mut(&mut self) -> &mut Self::Target {
1303 &mut self.base
1304 }
1305}
1306
1307impl ConstructorProvider<Node, Graph> for TileMap {
1308 fn constructor() -> NodeConstructor {
1309 NodeConstructor::new::<Self>()
1310 .with_variant("Tile Map", |_| {
1311 TileMapBuilder::new(BaseBuilder::new().with_name("Tile Map"))
1312 .build_node()
1313 .into()
1314 })
1315 .with_group("2D")
1316 }
1317}
1318
1319impl NodeTrait for TileMap {
1320 fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
1321 let Some(rect) = *self.bounding_rect() else {
1322 return AxisAlignedBoundingBox::default();
1323 };
1324
1325 let mut min_pos = rect.position.cast::<f32>().to_homogeneous();
1326 let mut max_pos = (rect.position + rect.size).cast::<f32>().to_homogeneous();
1327 min_pos.x *= -1.0;
1328 max_pos.x *= -1.0;
1329 let (min, max) = min_pos.inf_sup(&max_pos);
1330
1331 AxisAlignedBoundingBox::from_min_max(min, max)
1332 }
1333
1334 fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
1335 self.local_bounding_box()
1336 .transform(&self.global_transform())
1337 }
1338
1339 fn id(&self) -> Uuid {
1340 Self::type_uuid()
1341 }
1342
1343 fn collect_render_data(&self, ctx: &mut RenderContext) -> RdcControlFlow {
1344 if !self.should_be_rendered(ctx.frustum) {
1345 return RdcControlFlow::Continue;
1346 }
1347
1348 if renderer::is_shadow_pass(ctx.render_pass_name) {
1349 return RdcControlFlow::Continue;
1350 }
1351
1352 let Some(ref tile_set_resource) = *self.tile_set else {
1353 return RdcControlFlow::Continue;
1354 };
1355
1356 let mut tile_set_lock = TileSetRef::new(tile_set_resource);
1357 let tile_set = tile_set_lock.as_loaded();
1358
1359 let mut hidden_tiles = self.hidden_tiles.lock();
1360 hidden_tiles.clear();
1361
1362 let bounds = ctx
1363 .frustum
1364 .as_ref()
1365 .map(|f| self.cells_touching_frustum(f))
1366 .unwrap_or_default();
1367
1368 let mut tile_render_context = TileMapRenderContext {
1369 tile_map_handle: self.handle(),
1370 transform: self.tile_map_transform(),
1371 hidden_tiles: &mut hidden_tiles,
1372 context: ctx,
1373 bounds,
1374 tile_set,
1375 };
1376
1377 for effect in self.before_effects.iter() {
1378 effect.lock().render_special_tiles(&mut tile_render_context);
1379 }
1380 let bounds = tile_render_context.visible_bounds();
1381 let Some(tiles) = self.tiles.as_ref().map(|r| r.data_ref()) else {
1382 return RdcControlFlow::Continue;
1383 };
1384 let Some(tiles) = tiles.as_loaded_ref() else {
1385 return RdcControlFlow::Continue;
1386 };
1387 if bounds.is_some() {
1388 for (position, handle) in tiles.bounded_iter(bounds) {
1389 if bounds.contains(position) && tile_render_context.is_tile_visible(position) {
1390 let handle = tile_render_context.get_animated_version(handle);
1391 tile_render_context.draw_tile(position, handle);
1392 }
1393 }
1394 } else {
1395 for (position, handle) in tiles.iter() {
1396 if tile_render_context.is_tile_visible(position) {
1397 let handle = tile_render_context.get_animated_version(handle);
1398 tile_render_context.draw_tile(position, handle);
1399 }
1400 }
1401 }
1402 for effect in self.after_effects.iter() {
1403 effect.lock().render_special_tiles(&mut tile_render_context);
1404 }
1405 RdcControlFlow::Continue
1406 }
1407
1408 fn validate(&self, _scene: &Scene) -> Result<(), String> {
1409 if self.tile_set.is_none() {
1410 Err(
1411 "Tile set resource is not set. Tile map will not be rendered correctly!"
1412 .to_string(),
1413 )
1414 } else {
1415 Ok(())
1416 }
1417 }
1418}
1419
1420pub struct TileMapBuilder {
1422 base_builder: BaseBuilder,
1423 tile_set: Option<TileSetResource>,
1424 tiles: TileMapData,
1425 tile_scale: Vector2<f32>,
1426 before_effects: Vec<TileMapEffectRef>,
1427 after_effects: Vec<TileMapEffectRef>,
1428}
1429
1430impl TileMapBuilder {
1431 pub fn new(base_builder: BaseBuilder) -> Self {
1433 Self {
1434 base_builder,
1435 tile_set: None,
1436 tiles: TileMapData::default(),
1437 tile_scale: Vector2::repeat(1.0),
1438 before_effects: Default::default(),
1439 after_effects: Default::default(),
1440 }
1441 }
1442
1443 pub fn with_tile_set(mut self, tile_set: TileSetResource) -> Self {
1445 self.tile_set = Some(tile_set);
1446 self
1447 }
1448
1449 pub fn with_tiles(mut self, tiles: &Tiles) -> Self {
1451 for (pos, handle) in tiles.iter() {
1452 self.tiles.set(*pos, *handle);
1453 }
1454 self
1455 }
1456
1457 pub fn with_tile_scale(mut self, tile_scale: Vector2<f32>) -> Self {
1459 self.tile_scale = tile_scale;
1460 self
1461 }
1462
1463 pub fn with_before_effect(mut self, effect: TileMapEffectRef) -> Self {
1465 self.before_effects.push(effect);
1466 self
1467 }
1468
1469 pub fn with_after_effect(mut self, effect: TileMapEffectRef) -> Self {
1471 self.after_effects.push(effect);
1472 self
1473 }
1474
1475 pub fn build_node(self) -> Node {
1477 Node::new(TileMap {
1478 base: self.base_builder.build_base(),
1479 tile_set: self.tile_set.into(),
1480 tiles: Some(Resource::new_ok(ResourceKind::Embedded, self.tiles)).into(),
1481 tile_scale: self.tile_scale.into(),
1482 active_brush: Default::default(),
1483 hidden_tiles: Mutex::default(),
1484 before_effects: self.before_effects,
1485 after_effects: self.after_effects,
1486 })
1487 }
1488
1489 pub fn build(self, graph: &mut Graph) -> Handle<Node> {
1491 graph.add_node(self.build_node())
1492 }
1493}