1use fyrox_core::{futures::executor::block_on, log::Log};
31
32use crate::{
33 asset::{
34 io::ResourceIo,
35 loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader},
36 manager::ResourceManager,
37 state::LoadError,
38 untyped::UntypedResource,
39 Resource, ResourceData,
40 },
41 core::{
42 algebra::{Matrix4, Vector2, Vector3},
43 color::Color,
44 io::FileError,
45 reflect::prelude::*,
46 type_traits::prelude::*,
47 visitor::prelude::*,
48 },
49 scene::debug::SceneDrawingContext,
50};
51use std::{
52 error::Error,
53 fmt::{Display, Formatter},
54 path::{Path, PathBuf},
55 sync::Arc,
56};
57
58use super::*;
59
60#[derive(Debug)]
62pub enum TileMapBrushResourceError {
63 Io(FileError),
65
66 Visit(VisitError),
68}
69
70impl Display for TileMapBrushResourceError {
71 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
72 match self {
73 TileMapBrushResourceError::Io(v) => {
74 write!(f, "A file load error has occurred {v:?}")
75 }
76 TileMapBrushResourceError::Visit(v) => {
77 write!(
78 f,
79 "An error that may occur due to version incompatibilities. {v:?}"
80 )
81 }
82 }
83 }
84}
85
86impl From<FileError> for TileMapBrushResourceError {
87 fn from(e: FileError) -> Self {
88 Self::Io(e)
89 }
90}
91
92impl From<VisitError> for TileMapBrushResourceError {
93 fn from(e: VisitError) -> Self {
94 Self::Visit(e)
95 }
96}
97
98#[derive(Debug, Clone, Default, Reflect)]
103pub struct BrushMacroInstanceList(Vec<BrushMacroData>);
104
105impl BrushMacroInstanceList {
106 pub fn instances_with_uuid(&self, uuid: Uuid) -> impl Iterator<Item = &UntypedResource> {
108 self.0
109 .iter()
110 .filter(move |d| d.macro_id == uuid)
111 .filter_map(|d| d.settings.as_ref())
112 }
113}
114
115impl Deref for BrushMacroInstanceList {
116 type Target = Vec<BrushMacroData>;
117
118 fn deref(&self) -> &Self::Target {
119 &self.0
120 }
121}
122
123impl DerefMut for BrushMacroInstanceList {
124 fn deref_mut(&mut self) -> &mut Self::Target {
125 &mut self.0
126 }
127}
128
129impl Visit for BrushMacroInstanceList {
132 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
133 let mut region = visitor.enter_region(name)?;
134
135 let mut count = self.0.len() as u32;
136 count.visit("Count", &mut region)?;
137
138 if region.is_reading() {
139 self.clear();
140 for i in 0..(count as usize) {
141 let name = i.to_string();
142 let mut value = BrushMacroData::default();
144 match value.visit(&name, &mut region) {
145 Ok(()) => self.0.push(value),
146 Err(err) => Log::err(format!("Failed to load brush tool data due to: {err}")),
147 }
148 }
149 } else {
150 for (i, value) in self.0.iter_mut().enumerate() {
151 let name = i.to_string();
152 value.visit(&name, &mut region)?;
153 }
154 }
155
156 Ok(())
157 }
158}
159
160#[derive(Debug, Default, Clone, Visit, Reflect)]
163pub struct BrushMacroData {
164 pub macro_id: Uuid,
169 pub name: String,
171 pub settings: Option<UntypedResource>,
176}
177
178#[derive(Default, Debug, Clone, Visit, Reflect)]
181pub struct TileMapBrushPage {
182 pub icon: TileDefinitionHandle,
184 #[reflect(hidden)]
186 pub tiles: Tiles,
187}
188
189impl TileMapBrushPage {
190 pub fn bounding_rect(&self) -> OptionTileRect {
192 let mut result = OptionTileRect::default();
193 for pos in self.tiles.keys() {
194 result.push(*pos);
195 }
196 result
197 }
198 pub fn find_tile_at_position(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
200 self.tiles.get(&position).copied()
201 }
202 pub fn get_tiles<I: Iterator<Item = Vector2<i32>>>(&self, iter: I, tiles: &mut Tiles) {
204 for pos in iter {
205 if let Some(tile) = self.tiles.get(&pos).copied() {
206 tiles.insert(pos, tile);
207 }
208 }
209 }
210
211 pub fn draw_outline(
213 &self,
214 ctx: &mut SceneDrawingContext,
215 position: Vector2<i32>,
216 world_transform: &Matrix4<f32>,
217 color: Color,
218 ) {
219 for (pos, _) in self.tiles.iter() {
220 draw_tile_outline(ctx, position + pos, world_transform, color);
221 }
222 }
223}
224
225fn draw_tile_outline(
226 ctx: &mut SceneDrawingContext,
227 position: Vector2<i32>,
228 world_transform: &Matrix4<f32>,
229 color: Color,
230) {
231 ctx.draw_rectangle(
232 0.5,
233 0.5,
234 Matrix4::new_translation(
235 &(position.cast::<f32>().to_homogeneous() + Vector3::new(0.5, 0.5, 0.0)),
236 ) * world_transform,
237 color,
238 );
239}
240
241#[derive(Default, Debug, Clone, Visit, Reflect, TypeUuidProvider)]
244#[type_uuid(id = "23ed39da-cb01-4181-a058-94dc77ecb4b2")]
245pub struct TileMapBrush {
246 pub tile_set: Option<TileSetResource>,
249 #[reflect(hidden)]
254 pub pages: TileGridMap<TileMapBrushPage>,
255 #[visit(optional)]
257 pub macros: BrushMacroInstanceList,
258 #[reflect(hidden)]
260 #[visit(skip)]
261 pub change_flag: ChangeFlag,
262}
263
264impl TileMapBrush {
265 pub fn block_until_tile_set_is_loaded(&self) -> bool {
269 let Some(tile_set) = self.tile_set.as_ref() else {
270 return true;
271 };
272 let tile_set = match block_on(tile_set.clone()) {
273 Ok(tile_set) => tile_set,
274 Err(e) => {
275 Log::err(format!("Tile set load failed! Reason: {e:?}"));
276 return false;
277 }
278 };
279 if tile_set.is_ok() {
280 true
281 } else {
282 Log::err("Tile set load failed!");
283 false
284 }
285 }
286 pub fn tile_set(&self) -> Option<TileSetResource> {
289 let tile_set = self.tile_set.as_ref()?;
290 let tile_set = match block_on(tile_set.clone()) {
291 Ok(tile_set) => tile_set,
292 Err(e) => {
293 Log::err(format!("Tile set load failed! Reason: {e:?}"));
294 return None;
295 }
296 };
297 if !tile_set.is_ok() {
298 Log::err("Tile set load failed!");
299 return None;
300 }
301 Some(tile_set)
302 }
303 pub fn has_tile_at(&self, page: Vector2<i32>, tile: Vector2<i32>) -> bool {
305 let Some(page) = self.pages.get(&page) else {
306 return false;
307 };
308 page.tiles.contains_key(&tile)
309 }
310 pub fn has_page_at(&self, page: Vector2<i32>) -> bool {
312 self.pages.contains_key(&page)
313 }
314 pub fn tile_redirect(&self, handle: TileDefinitionHandle) -> Option<TileDefinitionHandle> {
316 self.find_tile_at_position(TilePaletteStage::Tiles, handle.page(), handle.tile())
317 }
318 #[inline]
320 pub fn pages_bounds(&self) -> OptionTileRect {
321 let mut result = OptionTileRect::default();
322 for pos in self.pages.keys() {
323 result.push(*pos);
324 }
325 result
326 }
327 pub fn page_icon(&self, page: Vector2<i32>) -> Option<TileDefinitionHandle> {
329 self.pages.get(&page).map(|p| p.icon)
330 }
331 pub fn tiles_bounds(&self, stage: TilePaletteStage, page: Vector2<i32>) -> OptionTileRect {
333 match stage {
334 TilePaletteStage::Tiles => {
335 let Some(page) = self.pages.get(&page) else {
336 return OptionTileRect::default();
337 };
338 page.bounding_rect()
339 }
340 TilePaletteStage::Pages => self.pages_bounds(),
341 }
342 }
343
344 pub fn find_tile_at_position(
346 &self,
347 stage: TilePaletteStage,
348 page: Vector2<i32>,
349 position: Vector2<i32>,
350 ) -> Option<TileDefinitionHandle> {
351 match stage {
352 TilePaletteStage::Pages => self.pages.get(&position).map(|p| p.icon),
353 TilePaletteStage::Tiles => self
354 .pages
355 .get(&page)
356 .and_then(|p| p.find_tile_at_position(position)),
357 }
358 }
359
360 pub fn get_tiles<I: Iterator<Item = Vector2<i32>>>(
362 &self,
363 stage: TilePaletteStage,
364 page: Vector2<i32>,
365 iter: I,
366 tiles: &mut Tiles,
367 ) {
368 match stage {
369 TilePaletteStage::Pages => {
370 for pos in iter {
371 if let Some(handle) = self.pages.get(&pos).map(|p| p.icon) {
372 tiles.insert(pos, handle);
373 }
374 }
375 }
376 TilePaletteStage::Tiles => {
377 if let Some(page) = self.pages.get(&page) {
378 page.get_tiles(iter, tiles);
379 }
380 }
381 }
382 }
383
384 pub fn is_missing_tile_set(&self) -> bool {
386 self.tile_set().is_none()
387 }
388
389 fn palette_render_loop_without_tile_set<F>(
390 &self,
391 stage: TilePaletteStage,
392 page: Vector2<i32>,
393 mut func: F,
394 ) where
395 F: FnMut(Vector2<i32>, TileRenderData),
396 {
397 match stage {
398 TilePaletteStage::Pages => {
399 for k in self.pages.keys() {
400 func(*k, TileRenderData::missing_data());
401 }
402 }
403 TilePaletteStage::Tiles => {
404 let Some(page) = self.pages.get(&page) else {
405 return;
406 };
407 for k in page.tiles.keys() {
408 func(*k, TileRenderData::missing_data());
409 }
410 }
411 }
412 }
413
414 pub fn palette_render_loop<F>(&self, stage: TilePaletteStage, page: Vector2<i32>, mut func: F)
417 where
418 F: FnMut(Vector2<i32>, TileRenderData),
419 {
420 let Some(tile_set) = self.tile_set() else {
421 self.palette_render_loop_without_tile_set(stage, page, func);
422 return;
423 };
424 let mut state = tile_set.state();
425 let Some(tile_set) = state.data() else {
426 self.palette_render_loop_without_tile_set(stage, page, func);
427 return;
428 };
429 match stage {
430 TilePaletteStage::Pages => {
431 for (k, p) in self.pages.iter() {
432 let data = if p.icon.is_empty() {
433 TileRenderData::empty()
434 } else {
435 tile_set
436 .get_tile_render_data(p.icon.into())
437 .unwrap_or_else(TileRenderData::missing_data)
438 };
439 func(*k, data);
440 }
441 }
442 TilePaletteStage::Tiles => {
443 let Some(page) = self.pages.get(&page) else {
444 return;
445 };
446 for (k, &handle) in page.tiles.iter() {
447 let data = if handle.is_empty() {
448 TileRenderData::empty()
449 } else {
450 tile_set
451 .get_tile_render_data(handle.into())
452 .unwrap_or_else(TileRenderData::missing_data)
453 };
454 func(*k, data);
455 }
456 }
457 }
458 }
459
460 pub fn get_tile_render_data(&self, position: ResourceTilePosition) -> Option<TileRenderData> {
465 let handle = self.redirect_handle(position)?;
466 if handle.is_empty() {
467 return Some(TileRenderData::empty());
468 }
469 let tile_set = self.tile_set()?;
470 let mut tile_set = tile_set.state();
471 let data = tile_set
472 .data()?
473 .get_tile_render_data(handle.into())
474 .unwrap_or_else(TileRenderData::missing_data);
475 Some(data)
476 }
477
478 pub fn redirect_handle(&self, position: ResourceTilePosition) -> Option<TileDefinitionHandle> {
483 match position.stage() {
484 TilePaletteStage::Tiles => {
485 let page = self.pages.get(&position.page())?;
486 page.tiles.get(&position.stage_position()).copied()
487 }
488 TilePaletteStage::Pages => self
489 .pages
490 .get(&position.stage_position())
491 .map(|page| page.icon),
492 }
493 }
494
495 pub fn stamp_element(&self, position: ResourceTilePosition) -> Option<StampElement> {
500 match position.stage() {
501 TilePaletteStage::Pages => self.redirect_handle(position).map(|handle| StampElement {
502 handle,
503 source: Some(position),
504 }),
505 TilePaletteStage::Tiles => {
506 let page = self.pages.get(&position.page())?;
507 Some(StampElement {
508 handle: *page.tiles.get(&position.stage_position())?,
509 source: Some(position),
510 })
511 }
512 }
513 }
514
515 pub fn get_tile_bounds(&self, position: ResourceTilePosition) -> Option<TileMaterialBounds> {
517 let handle = self.redirect_handle(position)?;
518 self.tile_set
519 .as_ref()?
520 .state()
521 .data()?
522 .get_tile_bounds(handle.into())
523 }
524
525 pub fn is_free_at(&self, position: ResourceTilePosition) -> bool {
527 match position.stage() {
528 TilePaletteStage::Pages => !self.pages.contains_key(&position.stage_position()),
529 TilePaletteStage::Tiles => !self
530 .pages
531 .get(&position.page())
532 .map(|p| p.tiles.contains_key(&position.stage_position()))
533 .unwrap_or_default(),
534 }
535 }
536
537 pub async fn from_file(
539 path: &Path,
540 resource_manager: ResourceManager,
541 io: &dyn ResourceIo,
542 ) -> Result<Self, TileMapBrushResourceError> {
543 let bytes = io.load_file(path).await?;
544 let mut visitor = Visitor::load_from_memory(&bytes)?;
545 visitor.blackboard.register(Arc::new(resource_manager));
546 let mut tile_map_brush = Self::default();
547 tile_map_brush.visit("TileMapBrush", &mut visitor)?;
548 Ok(tile_map_brush)
549 }
550
551 fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
552 let mut visitor = Visitor::new();
553 self.visit("TileMapBrush", &mut visitor)?;
554 visitor.save_ascii_to_file(path)?;
555 Ok(())
556 }
557}
558
559impl ResourceData for TileMapBrush {
560 fn type_uuid(&self) -> Uuid {
561 <Self as TypeUuidProvider>::type_uuid()
562 }
563
564 fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
565 self.save(path)
566 }
567
568 fn can_be_saved(&self) -> bool {
569 true
570 }
571
572 fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
573 Some(Box::new(self.clone()))
574 }
575}
576
577pub struct TileMapBrushLoader {
579 pub resource_manager: ResourceManager,
581}
582
583impl ResourceLoader for TileMapBrushLoader {
584 fn extensions(&self) -> &[&str] {
585 &["tile_map_brush"]
586 }
587
588 fn is_native_extension(&self, ext: &str) -> bool {
589 fyrox_core::cmp_strings_case_insensitive(ext, "tile_map_brush")
590 }
591
592 fn data_type_uuid(&self) -> Uuid {
593 <TileMapBrush as TypeUuidProvider>::type_uuid()
594 }
595
596 fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
597 let resource_manager = self.resource_manager.clone();
598 Box::pin(async move {
599 let tile_map_brush = TileMapBrush::from_file(&path, resource_manager, io.as_ref())
600 .await
601 .map_err(LoadError::new)?;
602 Ok(LoaderPayload::new(tile_map_brush))
603 })
604 }
605}
606
607pub type TileMapBrushResource = Resource<TileMapBrush>;