fyrox_impl/scene/tilemap/
brush.rs1use crate::{
31 asset::{
32 io::ResourceIo,
33 loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader},
34 manager::ResourceManager,
35 state::LoadError,
36 Resource, ResourceData,
37 },
38 core::{
39 algebra::{Matrix4, Vector2, Vector3},
40 color::Color,
41 io::FileLoadError,
42 reflect::prelude::*,
43 type_traits::prelude::*,
44 visitor::prelude::*,
45 },
46 scene::debug::SceneDrawingContext,
47};
48use std::{
49 error::Error,
50 fmt::{Display, Formatter},
51 path::{Path, PathBuf},
52 sync::Arc,
53};
54
55use super::*;
56
57#[derive(Debug)]
59pub enum TileMapBrushResourceError {
60 Io(FileLoadError),
62
63 Visit(VisitError),
65}
66
67impl Display for TileMapBrushResourceError {
68 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
69 match self {
70 TileMapBrushResourceError::Io(v) => {
71 write!(f, "A file load error has occurred {v:?}")
72 }
73 TileMapBrushResourceError::Visit(v) => {
74 write!(
75 f,
76 "An error that may occur due to version incompatibilities. {v:?}"
77 )
78 }
79 }
80 }
81}
82
83impl From<FileLoadError> for TileMapBrushResourceError {
84 fn from(e: FileLoadError) -> Self {
85 Self::Io(e)
86 }
87}
88
89impl From<VisitError> for TileMapBrushResourceError {
90 fn from(e: VisitError) -> Self {
91 Self::Visit(e)
92 }
93}
94
95#[derive(Default, Debug, Clone, Visit, Reflect)]
98pub struct TileMapBrushPage {
99 pub icon: TileDefinitionHandle,
101 #[reflect(hidden)]
103 pub tiles: Tiles,
104}
105
106impl TileSource for TileMapBrushPage {
107 fn transformation(&self) -> OrthoTransformation {
108 OrthoTransformation::default()
109 }
110 fn get_at(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
111 self.tiles.get(&position).copied()
112 }
113}
114
115impl TileMapBrushPage {
116 pub fn bounding_rect(&self) -> OptionTileRect {
118 let mut result = OptionTileRect::default();
119 for pos in self.tiles.keys() {
120 result.push(*pos);
121 }
122 result
123 }
124 pub fn find_tile_at_position(&self, position: Vector2<i32>) -> Option<TileDefinitionHandle> {
126 self.tiles.get(&position).copied()
127 }
128 pub fn get_tiles<I: Iterator<Item = Vector2<i32>>>(&self, iter: I, tiles: &mut Tiles) {
130 for pos in iter {
131 if let Some(tile) = self.tiles.get(&pos).copied() {
132 tiles.insert(pos, tile);
133 }
134 }
135 }
136
137 pub fn draw_outline(
139 &self,
140 ctx: &mut SceneDrawingContext,
141 position: Vector2<i32>,
142 world_transform: &Matrix4<f32>,
143 color: Color,
144 ) {
145 for (pos, _) in self.tiles.iter() {
146 draw_tile_outline(ctx, position + pos, world_transform, color);
147 }
148 }
149}
150
151fn draw_tile_outline(
152 ctx: &mut SceneDrawingContext,
153 position: Vector2<i32>,
154 world_transform: &Matrix4<f32>,
155 color: Color,
156) {
157 ctx.draw_rectangle(
158 0.5,
159 0.5,
160 Matrix4::new_translation(
161 &(position.cast::<f32>().to_homogeneous() + Vector3::new(0.5, 0.5, 0.0)),
162 ) * world_transform,
163 color,
164 );
165}
166
167#[derive(Default, Debug, Clone, Visit, Reflect, TypeUuidProvider)]
170#[type_uuid(id = "23ed39da-cb01-4181-a058-94dc77ecb4b2")]
171pub struct TileMapBrush {
172 pub tile_set: Option<TileSetResource>,
175 #[reflect(hidden)]
180 pub pages: TileGridMap<TileMapBrushPage>,
181 #[reflect(hidden)]
185 #[visit(skip)]
186 pub change_count: ChangeFlag,
187}
188
189impl TileMapBrush {
190 pub fn has_tile_at(&self, page: Vector2<i32>, tile: Vector2<i32>) -> bool {
192 let Some(page) = self.pages.get(&page) else {
193 return false;
194 };
195 page.tiles.contains_key(&tile)
196 }
197 pub fn has_page_at(&self, page: Vector2<i32>) -> bool {
199 self.pages.contains_key(&page)
200 }
201 pub fn tile_redirect(&self, handle: TileDefinitionHandle) -> Option<TileDefinitionHandle> {
203 self.find_tile_at_position(TilePaletteStage::Tiles, handle.page(), handle.tile())
204 }
205 #[inline]
207 pub fn pages_bounds(&self) -> OptionTileRect {
208 let mut result = OptionTileRect::default();
209 for pos in self.pages.keys() {
210 result.push(*pos);
211 }
212 result
213 }
214 pub fn page_icon(&self, page: Vector2<i32>) -> Option<TileDefinitionHandle> {
216 self.pages.get(&page).map(|p| p.icon)
217 }
218 pub fn tiles_bounds(&self, stage: TilePaletteStage, page: Vector2<i32>) -> OptionTileRect {
220 match stage {
221 TilePaletteStage::Tiles => {
222 let Some(page) = self.pages.get(&page) else {
223 return OptionTileRect::default();
224 };
225 page.bounding_rect()
226 }
227 TilePaletteStage::Pages => self.pages_bounds(),
228 }
229 }
230
231 pub fn find_tile_at_position(
233 &self,
234 stage: TilePaletteStage,
235 page: Vector2<i32>,
236 position: Vector2<i32>,
237 ) -> Option<TileDefinitionHandle> {
238 match stage {
239 TilePaletteStage::Pages => self.pages.get(&position).map(|p| p.icon),
240 TilePaletteStage::Tiles => self
241 .pages
242 .get(&page)
243 .and_then(|p| p.find_tile_at_position(position)),
244 }
245 }
246
247 pub fn get_tiles<I: Iterator<Item = Vector2<i32>>>(
249 &self,
250 stage: TilePaletteStage,
251 page: Vector2<i32>,
252 iter: I,
253 tiles: &mut Tiles,
254 ) {
255 match stage {
256 TilePaletteStage::Pages => {
257 for pos in iter {
258 if let Some(handle) = self.pages.get(&pos).map(|p| p.icon) {
259 tiles.insert(pos, handle);
260 }
261 }
262 }
263 TilePaletteStage::Tiles => {
264 if let Some(page) = self.pages.get(&page) {
265 page.get_tiles(iter, tiles);
266 }
267 }
268 }
269 }
270
271 pub fn is_missing_tile_set(&self) -> bool {
273 self.tile_set.is_none()
274 }
275
276 fn palette_render_loop_without_tile_set<F>(
277 &self,
278 stage: TilePaletteStage,
279 page: Vector2<i32>,
280 mut func: F,
281 ) where
282 F: FnMut(Vector2<i32>, TileRenderData),
283 {
284 match stage {
285 TilePaletteStage::Pages => {
286 for k in self.pages.keys() {
287 func(*k, TileRenderData::missing_data());
288 }
289 }
290 TilePaletteStage::Tiles => {
291 let Some(page) = self.pages.get(&page) else {
292 return;
293 };
294 for k in page.tiles.keys() {
295 func(*k, TileRenderData::missing_data());
296 }
297 }
298 }
299 }
300
301 pub fn palette_render_loop<F>(&self, stage: TilePaletteStage, page: Vector2<i32>, mut func: F)
304 where
305 F: FnMut(Vector2<i32>, TileRenderData),
306 {
307 let Some(tile_set) = self.tile_set.as_ref() else {
308 self.palette_render_loop_without_tile_set(stage, page, func);
309 return;
310 };
311 let mut state = tile_set.state();
312 let Some(tile_set) = state.data() else {
313 self.palette_render_loop_without_tile_set(stage, page, func);
314 return;
315 };
316 match stage {
317 TilePaletteStage::Pages => {
318 for (k, p) in self.pages.iter() {
319 let data = tile_set
320 .get_tile_render_data(p.icon.into())
321 .unwrap_or_else(TileRenderData::missing_data);
322 func(*k, data);
323 }
324 }
325 TilePaletteStage::Tiles => {
326 let Some(page) = self.pages.get(&page) else {
327 return;
328 };
329 for (k, &handle) in page.tiles.iter() {
330 let data = tile_set
331 .get_tile_render_data(handle.into())
332 .unwrap_or_else(TileRenderData::missing_data);
333 func(*k, data);
334 }
335 }
336 }
337 }
338
339 pub fn get_tile_render_data(&self, position: ResourceTilePosition) -> Option<TileRenderData> {
344 let handle = self.redirect_handle(position)?;
345 let mut tile_set = self.tile_set.as_ref()?.state();
346 let data = tile_set
347 .data()?
348 .get_tile_render_data(handle.into())
349 .unwrap_or_else(TileRenderData::missing_data);
350 Some(data)
351 }
352
353 pub fn redirect_handle(&self, position: ResourceTilePosition) -> Option<TileDefinitionHandle> {
358 match position.stage() {
359 TilePaletteStage::Tiles => {
360 let page = self.pages.get(&position.page())?;
361 page.tiles.get_at(position.stage_position())
362 }
363 TilePaletteStage::Pages => self
364 .pages
365 .get(&position.stage_position())
366 .map(|page| page.icon),
367 }
368 }
369
370 pub fn get_tile_bounds(&self, position: ResourceTilePosition) -> Option<TileMaterialBounds> {
372 let handle = self.redirect_handle(position)?;
373 self.tile_set
374 .as_ref()?
375 .state()
376 .data()?
377 .get_tile_bounds(handle.into())
378 }
379
380 pub async fn from_file(
382 path: &Path,
383 resource_manager: ResourceManager,
384 io: &dyn ResourceIo,
385 ) -> Result<Self, TileMapBrushResourceError> {
386 let bytes = io.load_file(path).await?;
387 let mut visitor = Visitor::load_from_memory(&bytes)?;
388 visitor.blackboard.register(Arc::new(resource_manager));
389 let mut tile_map_brush = Self::default();
390 tile_map_brush.visit("TileMapBrush", &mut visitor)?;
391 Ok(tile_map_brush)
392 }
393
394 fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
395 let mut visitor = Visitor::new();
396 self.visit("TileMapBrush", &mut visitor)?;
397 visitor.save_binary(path)?;
398 Ok(())
399 }
400}
401
402impl ResourceData for TileMapBrush {
403 fn type_uuid(&self) -> Uuid {
404 <Self as TypeUuidProvider>::type_uuid()
405 }
406
407 fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
408 self.save(path)
409 }
410
411 fn can_be_saved(&self) -> bool {
412 true
413 }
414}
415
416pub struct TileMapBrushLoader {
418 pub resource_manager: ResourceManager,
420}
421
422impl ResourceLoader for TileMapBrushLoader {
423 fn extensions(&self) -> &[&str] {
424 &["tile_map_brush"]
425 }
426
427 fn data_type_uuid(&self) -> Uuid {
428 <TileMapBrush as TypeUuidProvider>::type_uuid()
429 }
430
431 fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
432 let resource_manager = self.resource_manager.clone();
433 Box::pin(async move {
434 let tile_map_brush = TileMapBrush::from_file(&path, resource_manager, io.as_ref())
435 .await
436 .map_err(LoadError::new)?;
437 Ok(LoaderPayload::new(tile_map_brush))
438 })
439 }
440}
441
442pub type TileMapBrushResource = Resource<TileMapBrush>;