Skip to main content

fyrox_impl/scene/tilemap/
update.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! When a tile set, brush, or tile map is edited, the edit is first collected in
22//! an update object. This update object is rendered in place of the tiles that it will
23//! replace, and when the edit is completed the data in the update object is swapped
24//! with the data in the tile set/brush/tile map. The swap operation allows the edit
25//! to be undone by repeating the swap to put the original data back where it came from.
26//!
27//! [`TileSetUpdate`] is an update object that stores any of the many various ways in which
28//! a tile set may be modified, such as changing the color of tiles, changing the material of tiles,
29//! changing the value of a property or the shape of a collider.
30//!
31//! [`TransTilesUpdate`] stores tile definition handles and orthogonal transformations to be applied
32//! to those tiles before they are written into the object that is being edited. This can be used
33//! to construct either a `TileSetUpdate` or a `TilesUpdate` depending on whether we are editing
34//! a tile set or a tile map. `TransTilesUpdate` has methods for various tile-drawing operations
35//! like lines, rect fills, and flood fills.
36//!
37//! [`TilesUpdate`] stores simple tile definition handles with no transformations. Constructing this
38//! update is the final step before finally applying the modification to a tile map.
39
40use super::*;
41use crate::core::{algebra::Vector2, color::Color, log::Log, type_traits::prelude::*};
42use fxhash::FxHashMap;
43use fyrox_core::swap_hash_map_entry;
44use std::{
45    borrow::Cow,
46    collections::hash_map::Entry,
47    fmt::Debug,
48    ops::{Deref, DerefMut},
49};
50
51struct BresenhamLineIter {
52    dx: i32,
53    dy: i32,
54    x: i32,
55    y: i32,
56    error: i32,
57    end_x: i32,
58    is_steep: bool,
59    y_step: i32,
60}
61
62impl BresenhamLineIter {
63    fn new(start: Vector2<i32>, end: Vector2<i32>) -> BresenhamLineIter {
64        let (mut x0, mut y0) = (start.x, start.y);
65        let (mut x1, mut y1) = (end.x, end.y);
66
67        let is_steep = (y1 - y0).abs() > (x1 - x0).abs();
68        if is_steep {
69            std::mem::swap(&mut x0, &mut y0);
70            std::mem::swap(&mut x1, &mut y1);
71        }
72
73        if x0 > x1 {
74            std::mem::swap(&mut x0, &mut x1);
75            std::mem::swap(&mut y0, &mut y1);
76        }
77
78        let dx = x1 - x0;
79
80        BresenhamLineIter {
81            dx,
82            dy: (y1 - y0).abs(),
83            x: x0,
84            y: y0,
85            error: dx / 2,
86            end_x: x1,
87            is_steep,
88            y_step: if y0 < y1 { 1 } else { -1 },
89        }
90    }
91}
92
93impl Iterator for BresenhamLineIter {
94    type Item = Vector2<i32>;
95
96    fn next(&mut self) -> Option<Vector2<i32>> {
97        if self.x > self.end_x {
98            None
99        } else {
100            let ret = if self.is_steep {
101                Vector2::new(self.y, self.x)
102            } else {
103                Vector2::new(self.x, self.y)
104            };
105
106            self.x += 1;
107            self.error -= self.dy;
108            if self.error < 0 {
109                self.y += self.y_step;
110                self.error += self.dx;
111            }
112
113            Some(ret)
114        }
115    }
116}
117
118/// This represents a change to some pages of a tile set, without specifying which tile set.
119#[derive(Clone, Debug, Default)]
120pub struct TileSetUpdate(FxHashMap<TileDefinitionHandle, TileDataUpdate>);
121
122impl Deref for TileSetUpdate {
123    type Target = FxHashMap<TileDefinitionHandle, TileDataUpdate>;
124    fn deref(&self) -> &Self::Target {
125        &self.0
126    }
127}
128impl DerefMut for TileSetUpdate {
129    fn deref_mut(&mut self) -> &mut Self::Target {
130        &mut self.0
131    }
132}
133
134/// A change of material for some tile. Either the material is being erased,
135/// or it is being replaced by the given material.
136#[derive(Debug, Clone)]
137pub enum MaterialUpdate {
138    /// This update is eliminating the material from the tile.
139    Erase,
140    /// This update is replacing the material of the tile.
141    Replace(TileMaterialBounds),
142}
143
144/// This represents a change to a tile in some tile set.
145#[derive(Clone, Debug, Default)]
146pub enum TileDataUpdate {
147    /// Remove this tile.
148    #[default]
149    Erase,
150    /// Make no change to the tile.
151    DoNothing,
152    /// This variant is for changing a material page tile.
153    MaterialTile(TileData),
154    /// This variant is for changing a freeform page tile.
155    FreeformTile(TileDefinition),
156    /// This variant is for changing the transform of a tile.
157    /// This update must be applied to some cell of transform set page.
158    /// It contains the new source tile for the transform cell.
159    TransformSet(Option<TileDefinitionHandle>),
160    /// This variant is for changing a tile's color.
161    Color(Color),
162    /// This variant is for changing a tile's property.
163    Property(Uuid, Option<TileSetPropertyValue>),
164    /// This variant is for changing some of a tile property's nine slices.
165    PropertySlice(Uuid, [Option<i8>; 9]),
166    /// This variant is for changing a tile's collider.
167    Collider(FxHashMap<Uuid, TileCollider>),
168    /// This variant is for changing a tile's material.
169    Material(TileMaterialBounds),
170}
171
172impl TileDataUpdate {
173    /// Use this update to create a new property value based on the given property id and value.
174    pub fn apply_to_property_value(
175        &self,
176        property_id: Uuid,
177        value: TileSetPropertyValue,
178    ) -> TileSetPropertyValue {
179        match self {
180            TileDataUpdate::Erase => value.make_default(),
181            TileDataUpdate::DoNothing => value,
182            TileDataUpdate::MaterialTile(tile_data) => tile_data
183                .properties
184                .get(&property_id)
185                .cloned()
186                .unwrap_or(value.make_default()),
187            TileDataUpdate::FreeformTile(tile_definition) => tile_definition
188                .data
189                .properties
190                .get(&property_id)
191                .cloned()
192                .unwrap_or(value.make_default()),
193            TileDataUpdate::TransformSet(_) => value,
194            TileDataUpdate::Color(_) => value,
195            TileDataUpdate::Property(uuid, new_value) => {
196                if *uuid == property_id {
197                    new_value.as_ref().cloned().unwrap_or(value.make_default())
198                } else {
199                    value
200                }
201            }
202            TileDataUpdate::PropertySlice(uuid, data) => match value {
203                TileSetPropertyValue::NineSlice(mut old_data) if property_id == *uuid => {
204                    for (i, v) in data.iter().enumerate() {
205                        old_data.0[i] = v.unwrap_or(old_data.0[i]);
206                    }
207                    TileSetPropertyValue::NineSlice(old_data)
208                }
209                _ if property_id == *uuid => {
210                    TileSetPropertyValue::NineSlice(NineI8(data.map(|x| x.unwrap_or_default())))
211                }
212                _ => value,
213            },
214            TileDataUpdate::Collider(_) => value,
215            TileDataUpdate::Material(_) => value,
216        }
217    }
218    /// The tile collider for the given id, if the collider is being replaced by this update.
219    /// None if the collider is not changed by this update.
220    pub fn get_tile_collider(&self, uuid: &Uuid) -> Option<&TileCollider> {
221        match self {
222            TileDataUpdate::Erase => Some(&TileCollider::None),
223            TileDataUpdate::MaterialTile(data) => {
224                data.colliders.get(uuid).or(Some(&TileCollider::None))
225            }
226            TileDataUpdate::FreeformTile(def) => {
227                def.data.colliders.get(uuid).or(Some(&TileCollider::None))
228            }
229            TileDataUpdate::Collider(map) => map.get(uuid),
230            _ => None,
231        }
232    }
233    /// The handle that should be used in place of the given handle, if this update has changed
234    /// the handle of a transform set tile.
235    /// None is returned if no tile should be rendered.
236    /// The given tile is returned if no change should be made.
237    pub fn substitute_transform_handle(
238        &self,
239        source: TileDefinitionHandle,
240    ) -> Option<TileDefinitionHandle> {
241        if let TileDataUpdate::TransformSet(new_source) = self {
242            *new_source
243        } else {
244            Some(source)
245        }
246    }
247    /// The render data that should be used in place of the given render data, based on this update.
248    /// None is returned if no tile should be rendered.
249    pub fn modify_render<'a>(&self, source: &'a TileRenderData) -> Option<Cow<'a, TileRenderData>> {
250        match self {
251            TileDataUpdate::Erase => None,
252            TileDataUpdate::MaterialTile(tile_data) => Some(Cow::Owned(TileRenderData::new(
253                source.material_bounds.clone(),
254                tile_data.color,
255            ))),
256            TileDataUpdate::FreeformTile(def) => Some(Cow::Owned(TileRenderData::new(
257                Some(def.material_bounds.clone()),
258                def.data.color,
259            ))),
260            TileDataUpdate::Color(color) => Some(Cow::Owned(TileRenderData::new(
261                source.material_bounds.clone(),
262                *color,
263            ))),
264            TileDataUpdate::Material(material_bounds) => Some(Cow::Owned(TileRenderData::new(
265                Some(material_bounds.clone()),
266                source.color,
267            ))),
268            _ => Some(Cow::Borrowed(source)),
269        }
270    }
271    /// Remove `TileData` and turn this object into `Erase`, if this is a MaterialTile. Otherwise, panic.
272    pub fn take_data(&mut self) -> TileData {
273        match std::mem::take(self) {
274            TileDataUpdate::MaterialTile(d) => d,
275            _ => panic!(),
276        }
277    }
278    /// Remove `TileDefinition` and turn this object into `Erase`, if this is a FreeformTile. Otherwise, panic.
279    pub fn take_definition(&mut self) -> TileDefinition {
280        match std::mem::take(self) {
281            TileDataUpdate::FreeformTile(d) => d,
282            _ => panic!(),
283        }
284    }
285    /// Swap whatever value is in this tile update with the corresponding value in the given TileData.
286    /// If this update has no data to swap, then do nothing and set this update to `DoNothing`.
287    pub fn swap_with_data(&mut self, data: &mut TileData) {
288        match self {
289            TileDataUpdate::DoNothing => (),
290            TileDataUpdate::Erase => {
291                Log::err("Tile data swap error");
292                *self = Self::DoNothing;
293            }
294            TileDataUpdate::MaterialTile(tile_data) => std::mem::swap(tile_data, data),
295            TileDataUpdate::FreeformTile(tile_definition) => {
296                std::mem::swap(&mut tile_definition.data, data)
297            }
298            TileDataUpdate::Color(color) => std::mem::swap(color, &mut data.color),
299            TileDataUpdate::Collider(colliders) => {
300                for (uuid, value) in colliders.iter_mut() {
301                    match data.colliders.entry(*uuid) {
302                        Entry::Occupied(mut e) => {
303                            if let TileCollider::None = value {
304                                *value = e.remove();
305                            } else {
306                                std::mem::swap(e.get_mut(), value)
307                            }
308                        }
309                        Entry::Vacant(e) => {
310                            e.insert(value.clone());
311                            *value = TileCollider::None;
312                        }
313                    }
314                }
315            }
316            TileDataUpdate::Property(uuid, value) => {
317                swap_hash_map_entry(data.properties.entry(*uuid), value)
318            }
319            TileDataUpdate::PropertySlice(uuid, value) => match data.properties.entry(*uuid) {
320                Entry::Occupied(mut e) => {
321                    if let TileSetPropertyValue::NineSlice(v0) = e.get_mut() {
322                        for (v0, v1) in v0.0.iter_mut().zip(value.iter_mut()) {
323                            if let Some(v1) = v1 {
324                                std::mem::swap(v0, v1);
325                            }
326                        }
327                    }
328                }
329                Entry::Vacant(e) => {
330                    let _ = e.insert(TileSetPropertyValue::NineSlice(NineI8(
331                        value.map(|v| v.unwrap_or_default()),
332                    )));
333                    *self = TileDataUpdate::Property(*uuid, None);
334                }
335            },
336            TileDataUpdate::TransformSet(_) => {
337                Log::err("Tile data swap error");
338                *self = Self::DoNothing;
339            }
340            TileDataUpdate::Material(_) => {
341                Log::err("Tile data swap error");
342                *self = Self::DoNothing;
343            }
344        }
345    }
346}
347
348impl TileSetUpdate {
349    /// Attempt to fill this TileSetUpdate based upon a TransTilesUpdate.
350    /// The TransTilesUpdate contains only positions, transformations, and TileDefinitionHandles for the tiles that are to be written.
351    /// In order to construct a TileSetUpdate, we use the given TileSet to copy tile bounds and tile definition data
352    /// as appropriate for the kind of page we are updating.
353    ///
354    /// Nothing is done if the given page does not exist or if it is a Material page that cannot be written to.
355    pub fn convert(
356        &mut self,
357        tiles: &TransTilesUpdate,
358        tile_set: &TileSetResource,
359        page: Vector2<i32>,
360        source_set: &TileSetResource,
361    ) {
362        let tile_set = tile_set.data_ref();
363        let Some(page_object) = tile_set.get_page(page) else {
364            return;
365        };
366        match &page_object.source {
367            TileSetPageSource::Atlas(_) => self.convert_material(tiles, page),
368            TileSetPageSource::Freeform(_) => {
369                drop(tile_set);
370                self.convert_freeform(tiles, &TileSetRef::new(source_set).as_loaded(), page);
371            }
372            TileSetPageSource::Transform(_) | TileSetPageSource::Animation(_) => {
373                drop(tile_set);
374                self.convert_transform(tiles, &TileSetRef::new(source_set).as_loaded(), page);
375            }
376        }
377    }
378    fn convert_material(&mut self, tiles: &TransTilesUpdate, page: Vector2<i32>) {
379        for (pos, value) in tiles.iter() {
380            let Some(handle) = TileDefinitionHandle::try_new(page, *pos) else {
381                continue;
382            };
383            if value.is_some() {
384                self.insert(handle, TileDataUpdate::MaterialTile(TileData::default()));
385            } else {
386                self.insert(handle, TileDataUpdate::Erase);
387            }
388        }
389    }
390    fn convert_freeform(
391        &mut self,
392        tiles: &TransTilesUpdate,
393        tile_set: &OptionTileSet,
394        page: Vector2<i32>,
395    ) {
396        for (pos, value) in tiles.iter() {
397            let Some(handle) = TileDefinitionHandle::try_new(page, *pos) else {
398                continue;
399            };
400            if let Some(def) = value
401                .as_ref()
402                .map(|v| v.pair())
403                .and_then(|(t, h)| tile_set.get_transformed_definition(t, h))
404            {
405                self.insert(handle, TileDataUpdate::FreeformTile(def));
406            } else {
407                self.insert(handle, TileDataUpdate::Erase);
408            }
409        }
410    }
411    fn convert_transform(
412        &mut self,
413        tiles: &TransTilesUpdate,
414        tile_set: &OptionTileSet,
415        page: Vector2<i32>,
416    ) {
417        for (pos, value) in tiles.iter() {
418            let Some(target_handle) = TileDefinitionHandle::try_new(page, *pos) else {
419                continue;
420            };
421            if let Some((trans, handle)) = value.as_ref().map(|v| v.pair()) {
422                let handle = tile_set
423                    .get_transformed_version(trans, handle)
424                    .unwrap_or(handle);
425                self.insert(target_handle, TileDataUpdate::TransformSet(Some(handle)));
426            } else {
427                self.insert(target_handle, TileDataUpdate::TransformSet(None));
428            }
429        }
430    }
431    /// Get the color being set onto the given tile by this update, if a color is being set.
432    pub fn get_color(&self, page: Vector2<i32>, position: Vector2<i32>) -> Option<Color> {
433        let handle = TileDefinitionHandle::try_new(page, position)?;
434        match self.get(&handle)? {
435            TileDataUpdate::Erase => Some(Color::default()),
436            TileDataUpdate::MaterialTile(data) => Some(data.color),
437            TileDataUpdate::FreeformTile(def) => Some(def.data.color),
438            TileDataUpdate::Color(color) => Some(*color),
439            _ => None,
440        }
441    }
442    /// Get the material being set onto the given tile by this update, if a material is being set.
443    pub fn get_material(
444        &self,
445        page: Vector2<i32>,
446        position: Vector2<i32>,
447    ) -> Option<MaterialUpdate> {
448        let handle = TileDefinitionHandle::try_new(page, position)?;
449        match self.get(&handle)? {
450            TileDataUpdate::Erase => Some(MaterialUpdate::Erase),
451            TileDataUpdate::FreeformTile(def) => {
452                Some(MaterialUpdate::Replace(def.material_bounds.clone()))
453            }
454            TileDataUpdate::Material(mat) => Some(MaterialUpdate::Replace(mat.clone())),
455            _ => None,
456        }
457    }
458    /// Get the tile bounds being set onto the given tile by this update, if possible.
459    pub fn get_tile_bounds(
460        &self,
461        page: Vector2<i32>,
462        position: Vector2<i32>,
463    ) -> Option<TileBounds> {
464        let handle = TileDefinitionHandle::try_new(page, position)?;
465        match self.get(&handle)? {
466            TileDataUpdate::Erase => Some(TileBounds::default()),
467            TileDataUpdate::FreeformTile(def) => Some(def.material_bounds.bounds.clone()),
468            TileDataUpdate::Material(mat) => Some(mat.bounds.clone()),
469            _ => None,
470        }
471    }
472    /// Get the value of the given property being set onto the given tile by this update, if possible.
473    pub fn get_property(
474        &self,
475        page: Vector2<i32>,
476        position: Vector2<i32>,
477        property_id: Uuid,
478    ) -> Option<Option<TileSetPropertyValue>> {
479        let handle = TileDefinitionHandle::try_new(page, position)?;
480        match self.get(&handle)? {
481            TileDataUpdate::Erase => Some(None),
482            TileDataUpdate::MaterialTile(data) => Some(data.properties.get(&property_id).cloned()),
483            TileDataUpdate::FreeformTile(def) => {
484                Some(def.data.properties.get(&property_id).cloned())
485            }
486            TileDataUpdate::Property(id, value) if *id == property_id => Some(value.clone()),
487            _ => None,
488        }
489    }
490    /// Get the value of the given collider being set onto the given tile by this update, if possible.
491    pub fn get_collider(
492        &self,
493        page: Vector2<i32>,
494        position: Vector2<i32>,
495        collider_id: &Uuid,
496    ) -> Option<&TileCollider> {
497        let handle = TileDefinitionHandle::try_new(page, position)?;
498        self.get(&handle)?.get_tile_collider(collider_id)
499    }
500    /// Set the given color on the given tile.
501    pub fn set_color(&mut self, page: Vector2<i32>, position: Vector2<i32>, color: Color) {
502        if let Some(handle) = TileDefinitionHandle::try_new(page, position) {
503            self.insert(handle, TileDataUpdate::Color(color));
504        }
505    }
506    /// Set the given property value on the given tile.
507    pub fn set_property(
508        &mut self,
509        page: Vector2<i32>,
510        position: Vector2<i32>,
511        property_id: Uuid,
512        value: Option<TileSetPropertyValue>,
513    ) {
514        if let Some(handle) = TileDefinitionHandle::try_new(page, position) {
515            self.insert(handle, TileDataUpdate::Property(property_id, value));
516        }
517    }
518    /// Set the given value to the given slice of the given property of the given tile.
519    pub fn set_property_slice(
520        &mut self,
521        page: Vector2<i32>,
522        position: Vector2<i32>,
523        subposition: Vector2<usize>,
524        property_id: Uuid,
525        value: i8,
526    ) {
527        use TileSetPropertyValue as PropValue;
528        let index = TileSetPropertyValue::nine_position_to_index(subposition);
529        if let Some(handle) = TileDefinitionHandle::try_new(page, position) {
530            match self.entry(handle) {
531                Entry::Occupied(mut e) => match e.get_mut() {
532                    TileDataUpdate::PropertySlice(uuid, d0) if *uuid == property_id => {
533                        d0[index] = Some(value);
534                    }
535                    TileDataUpdate::Property(uuid, Some(PropValue::NineSlice(d0)))
536                        if *uuid == property_id =>
537                    {
538                        d0.0[index] = value;
539                    }
540                    d0 => {
541                        let mut data = [0; 9];
542                        data[index] = value;
543                        *d0 = TileDataUpdate::Property(
544                            property_id,
545                            Some(PropValue::NineSlice(NineI8(data))),
546                        );
547                    }
548                },
549                Entry::Vacant(e) => {
550                    let mut data = [None; 9];
551                    data[index] = Some(value);
552                    let _ = e.insert(TileDataUpdate::PropertySlice(property_id, data));
553                }
554            }
555        }
556    }
557    /// Set the given property value on the given tile.
558    pub fn set_collider<I: Iterator<Item = Uuid>>(
559        &mut self,
560        page: Vector2<i32>,
561        position: Vector2<i32>,
562        property_ids: I,
563        value: &TileCollider,
564    ) {
565        let Some(handle) = TileDefinitionHandle::try_new(page, position) else {
566            return;
567        };
568        let mut colliders = FxHashMap::default();
569        colliders.extend(property_ids.map(|uuid| (uuid, value.clone())));
570        self.insert(handle, TileDataUpdate::Collider(colliders));
571    }
572    /// Set the given material on the given tile.
573    pub fn set_material(
574        &mut self,
575        page: Vector2<i32>,
576        position: Vector2<i32>,
577        value: TileMaterialBounds,
578    ) {
579        if let Some(handle) = TileDefinitionHandle::try_new(page, position) {
580            self.insert(handle, TileDataUpdate::Material(value));
581        }
582    }
583}
584
585/// A stamp element plus the transformation of the [`Stamp`] where the element is taken from,
586/// allowing the stamp element itself to be later transformed before it is applied to a tile map,
587/// tile set, or brush.
588#[derive(Debug, Clone, Reflect)]
589pub struct RotTileHandle {
590    /// The transformation of the element.
591    pub transform: OrthoTransformation,
592    /// The element of the stamp.
593    pub element: StampElement,
594}
595
596impl RotTileHandle {
597    /// The transformation and the tile handle.
598    pub fn pair(&self) -> (OrthoTransformation, TileDefinitionHandle) {
599        (self.transform, self.element.handle)
600    }
601}
602
603/// This is a step in the process of performing an edit to a tile map, brush, or tile set.
604/// It provides handles for the tiles to be written and the transformation to apply to those
605/// tiles. A None indicates that the tile is to be erased.
606#[derive(Clone, Debug, Default)]
607pub struct TransTilesUpdate(TileGridMap<Option<RotTileHandle>>);
608
609/// This is a step in the process of performing an edit to a tile map when macros are involved.
610/// It contains the tiles to be written and the positions within the brush that the tiles were
611/// taken from. It is constructed from a [`TransTilesUpdate`] by applying the transformation
612/// to the tiles. A None indicates that the tile is to be erased.
613#[derive(Clone, Default)]
614pub struct MacroTilesUpdate(TileGridMap<Option<StampElement>>);
615
616impl Debug for MacroTilesUpdate {
617    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
618        f.write_str("MacroTilesUpdate")?;
619        for (p, v) in self.0.iter() {
620            write!(f, " ({:2},{:2})->", p.x, p.y)?;
621            if let Some(StampElement { handle, source }) = v {
622                write!(f, "{handle}")?;
623                if let Some(cell) = source {
624                    write!(f, "[{cell}]")?;
625                }
626            } else {
627                f.write_str("Delete")?;
628            }
629        }
630        Ok(())
631    }
632}
633
634/// A set of changes to a set of tiles. A value of None indicates that a tile
635/// is being removed from the set. A None indicates that the tile is to be erased.
636#[derive(Clone, Debug, Default, PartialEq)]
637pub struct TilesUpdate(TileGridMap<Option<TileDefinitionHandle>>);
638
639impl Deref for TilesUpdate {
640    type Target = TileGridMap<Option<TileDefinitionHandle>>;
641
642    fn deref(&self) -> &Self::Target {
643        &self.0
644    }
645}
646
647impl DerefMut for TilesUpdate {
648    fn deref_mut(&mut self) -> &mut Self::Target {
649        &mut self.0
650    }
651}
652
653impl Deref for MacroTilesUpdate {
654    type Target = TileGridMap<Option<StampElement>>;
655
656    fn deref(&self) -> &Self::Target {
657        &self.0
658    }
659}
660
661impl DerefMut for MacroTilesUpdate {
662    fn deref_mut(&mut self) -> &mut Self::Target {
663        &mut self.0
664    }
665}
666
667impl Deref for TransTilesUpdate {
668    type Target = TileGridMap<Option<RotTileHandle>>;
669
670    fn deref(&self) -> &Self::Target {
671        &self.0
672    }
673}
674
675impl DerefMut for TransTilesUpdate {
676    fn deref_mut(&mut self) -> &mut Self::Target {
677        &mut self.0
678    }
679}
680
681impl MacroTilesUpdate {
682    /// Construct a TilesUpdate by stripping out the brush cell information.
683    pub fn build_tiles_update(&self) -> TilesUpdate {
684        let mut result = TilesUpdate::default();
685        for (pos, value) in self.iter() {
686            let handle = value.as_ref().map(|e| e.handle);
687            result.insert(*pos, handle);
688        }
689        result
690    }
691    /// Replace the values of a TransTilesUpdate using the identity transformation. This allows an update to be
692    /// converted back to a `TransTilesUpdates` after macro processing.
693    pub fn fill_trans_tiles_update(&self, tiles_update: &mut TransTilesUpdate) {
694        tiles_update.clear();
695        for (pos, tile) in self.iter() {
696            let value = tile.as_ref().cloned().map(|element| RotTileHandle {
697                transform: OrthoTransformation::identity(),
698                element,
699            });
700            tiles_update.insert(*pos, value);
701        }
702    }
703}
704
705impl TransTilesUpdate {
706    /// Construct a TilesUpdate by finding the transformed version of each tile
707    /// in the given tile set.
708    pub fn build_tiles_update(&self, tile_set: &OptionTileSet) -> TilesUpdate {
709        let mut result = TilesUpdate::default();
710        for (pos, value) in self.iter() {
711            if let Some((trans, handle)) = value.as_ref().map(|v| v.pair()) {
712                let handle = tile_set
713                    .get_transformed_version(trans, handle)
714                    .unwrap_or(handle);
715                result.insert(*pos, Some(handle));
716            } else {
717                result.insert(*pos, None);
718            }
719        }
720        result
721    }
722    /// Construct a TilesUpdate by finding the transformed version of each tile
723    /// in the given tile set.
724    pub fn fill_macro_tiles_update(
725        &self,
726        tile_set: &OptionTileSet,
727        macro_update: &mut MacroTilesUpdate,
728    ) {
729        macro_update.clear();
730        for (pos, value) in self.iter() {
731            if let Some(RotTileHandle { transform, element }) = value.as_ref() {
732                let handle = element.handle;
733                let handle = tile_set
734                    .get_transformed_version(*transform, handle)
735                    .unwrap_or(handle);
736                macro_update.insert(
737                    *pos,
738                    Some(StampElement {
739                        handle,
740                        source: element.source,
741                    }),
742                );
743            } else {
744                macro_update.insert(*pos, None);
745            }
746        }
747    }
748    /// Fills the given tiles at the given point using tiles from the given source. This method
749    /// extends tile map when trying to fill at a point that lies outside the bounding rectangle.
750    /// Keep in mind, that flood fill is only possible either on free cells or on cells with the same
751    /// tile kind. Modifications to the tile source are written into the given TileUpdates object
752    /// rather than modifying the tiles directly.
753    pub fn flood_fill<T: BoundedTileSource, S: TileSource>(
754        &mut self,
755        tiles: &T,
756        start_point: Vector2<i32>,
757        brush: &S,
758    ) {
759        let mut bounds = tiles.bounding_rect();
760        bounds.push(start_point);
761
762        let allowed_definition = tiles.get_at(start_point).map(|t| t.handle);
763        let mut stack = vec![start_point];
764        while let Some(position) = stack.pop() {
765            let definition = tiles.get_at(position).map(|t| t.handle);
766            if definition == allowed_definition && !self.contains_key(&position) {
767                let value = brush.get_at(position).map(|element| RotTileHandle {
768                    transform: brush.transformation(),
769                    element,
770                });
771                self.insert(position, value);
772
773                // Continue on neighbours.
774                for neighbour_position in [
775                    Vector2::new(position.x - 1, position.y),
776                    Vector2::new(position.x + 1, position.y),
777                    Vector2::new(position.x, position.y - 1),
778                    Vector2::new(position.x, position.y + 1),
779                ] {
780                    if bounds.contains(neighbour_position) {
781                        stack.push(neighbour_position);
782                    }
783                }
784            }
785        }
786    }
787    /// Draws the given tiles on the tile map
788    #[inline]
789    pub fn draw_tiles(&mut self, origin: Vector2<i32>, brush: &Stamp) {
790        let transform = brush.transformation();
791        for (local_position, element) in brush.iter() {
792            let element = element.clone();
793            self.insert(
794                origin + local_position,
795                Some(RotTileHandle { transform, element }),
796            );
797        }
798    }
799    /// Erases the tiles under the given brush.
800    #[inline]
801    pub fn erase_stamp(&mut self, origin: Vector2<i32>, brush: &Stamp) {
802        for local_position in brush.keys() {
803            self.insert(origin + local_position, None);
804        }
805    }
806    /// Erases the given tile.
807    pub fn erase(&mut self, position: Vector2<i32>) {
808        self.insert(position, None);
809    }
810    /// Fills the given rectangle using the given stamp.
811    pub fn rect_fill(&mut self, start: Vector2<i32>, end: Vector2<i32>, stamp: &Stamp) {
812        let region = TileRegion::from_points(start, end);
813        let stamp_source = stamp.repeat(start, end);
814        self.rect_fill_inner(region, &stamp_source);
815    }
816    /// Fills the given rectangle using random tiles from the given stamp.
817    pub fn rect_fill_random(&mut self, start: Vector2<i32>, end: Vector2<i32>, stamp: &Stamp) {
818        let region = TileRegion::from_points(start, end);
819        self.rect_fill_inner(region, &RandomTileSource(stamp));
820    }
821    /// Fills the given rectangle using the given tiles.
822    fn rect_fill_inner<S: TileSource>(&mut self, region: TileRegion, brush: &S) {
823        let transform = brush.transformation();
824        for (target, source) in region.iter() {
825            if let Some(element) = brush.get_at(source) {
826                self.insert(target, Some(RotTileHandle { transform, element }));
827            }
828        }
829    }
830    /// Draw a line from a point to point.
831    pub fn draw_line<S: TileSource>(&mut self, from: Vector2<i32>, to: Vector2<i32>, brush: &S) {
832        let transform = brush.transformation();
833        for position in BresenhamLineIter::new(from, to) {
834            if let Some(element) = brush.get_at(position - from) {
835                self.insert(position, Some(RotTileHandle { transform, element }));
836            }
837        }
838    }
839
840    /// Fills in a rectangle using special brush with 3x3 tiles. It puts
841    /// corner tiles in the respective corners of the target rectangle and draws lines between each
842    /// corner using middle tiles.
843    pub fn nine_slice(&mut self, start: Vector2<i32>, end: Vector2<i32>, brush: &Stamp) {
844        self.nine_slice_inner(
845            start,
846            end,
847            brush,
848            |update, target_region, source, source_region| {
849                update.rect_fill_inner(
850                    target_region,
851                    &RepeatTileSource {
852                        source,
853                        region: source_region,
854                    },
855                )
856            },
857        );
858    }
859    /// Fills in a rectangle using special brush with 3x3 tiles. It puts
860    /// corner tiles in the respective corners of the target rectangle and draws lines between each
861    /// corner using middle tiles shuffled into random order.
862    pub fn nine_slice_random(&mut self, start: Vector2<i32>, end: Vector2<i32>, brush: &Stamp) {
863        self.nine_slice_inner(
864            start,
865            end,
866            brush,
867            |update, target_region, source, source_region| {
868                update.rect_fill_inner(
869                    target_region,
870                    &PartialRandomTileSource(source, source_region.bounds),
871                )
872            },
873        );
874    }
875
876    /// Fills in a rectangle using special brush with 3x3 tiles. It puts
877    /// corner tiles in the respective corners of the target rectangle and draws lines between each
878    /// corner using middle tiles.
879    #[inline]
880    fn nine_slice_inner<F>(
881        &mut self,
882        start: Vector2<i32>,
883        end: Vector2<i32>,
884        stamp: &Stamp,
885        fill: F,
886    ) where
887        F: Fn(&mut TransTilesUpdate, TileRegion, &Stamp, TileRegion),
888    {
889        let Some(stamp_rect) = *stamp.bounding_rect() else {
890            return;
891        };
892        let rect = TileRect::from_points(start, end);
893        let region = TileRegion {
894            origin: start,
895            bounds: rect.into(),
896        };
897        let inner_region = region.clone().deflate(1, 1);
898
899        let stamp_region = TileRegion::from_bounds_and_direction(stamp_rect.into(), start - end);
900        let inner_stamp_region = stamp_region.clone().deflate(1, 1);
901
902        // Place corners first.
903        let transform = stamp.transformation();
904        for (corner_position, actual_corner_position) in [
905            (stamp_rect.left_top_corner(), rect.left_top_corner()),
906            (stamp_rect.right_top_corner(), rect.right_top_corner()),
907            (stamp_rect.right_bottom_corner(), rect.right_bottom_corner()),
908            (stamp_rect.left_bottom_corner(), rect.left_bottom_corner()),
909        ] {
910            if let Some(element) = stamp.get(corner_position).cloned() {
911                self.insert(
912                    actual_corner_position,
913                    Some(RotTileHandle { transform, element }),
914                );
915            }
916        }
917
918        let top = region.clone().with_bounds(
919            TileRect::from_points(
920                rect.left_top_corner() + Vector2::new(1, 0),
921                rect.right_top_corner() + Vector2::new(-1, 0),
922            )
923            .into(),
924        );
925        let bottom = region.clone().with_bounds(
926            TileRect::from_points(
927                rect.left_bottom_corner() + Vector2::new(1, 0),
928                rect.right_bottom_corner() + Vector2::new(-1, 0),
929            )
930            .into(),
931        );
932        let left = region.clone().with_bounds(
933            TileRect::from_points(
934                rect.left_bottom_corner() + Vector2::new(0, 1),
935                rect.left_top_corner() + Vector2::new(0, -1),
936            )
937            .into(),
938        );
939        let right = region.clone().with_bounds(
940            TileRect::from_points(
941                rect.right_bottom_corner() + Vector2::new(0, 1),
942                rect.right_top_corner() + Vector2::new(0, -1),
943            )
944            .into(),
945        );
946        let stamp_top = stamp_region.clone().with_bounds(
947            TileRect::from_points(
948                stamp_rect.left_top_corner() + Vector2::new(1, 0),
949                stamp_rect.right_top_corner() + Vector2::new(-1, 0),
950            )
951            .into(),
952        );
953        let stamp_bottom = stamp_region.clone().with_bounds(
954            TileRect::from_points(
955                stamp_rect.left_bottom_corner() + Vector2::new(1, 0),
956                stamp_rect.right_bottom_corner() + Vector2::new(-1, 0),
957            )
958            .into(),
959        );
960        let stamp_left = stamp_region.clone().with_bounds(
961            TileRect::from_points(
962                stamp_rect.left_bottom_corner() + Vector2::new(0, 1),
963                stamp_rect.left_top_corner() + Vector2::new(0, -1),
964            )
965            .into(),
966        );
967        let stamp_right = stamp_region.clone().with_bounds(
968            TileRect::from_points(
969                stamp_rect.right_bottom_corner() + Vector2::new(0, 1),
970                stamp_rect.right_top_corner() + Vector2::new(0, -1),
971            )
972            .into(),
973        );
974
975        if rect.size.x > 2 && stamp_rect.size.x > 2 {
976            fill(self, top, stamp, stamp_top);
977            fill(self, bottom, stamp, stamp_bottom);
978        }
979        if rect.size.y > 2 && stamp_rect.size.y > 2 {
980            fill(self, left, stamp, stamp_left);
981            fill(self, right, stamp, stamp_right);
982        }
983        fill(self, inner_region, stamp, inner_stamp_region);
984    }
985}