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    ops::{Deref, DerefMut},
48};
49
50struct BresenhamLineIter {
51    dx: i32,
52    dy: i32,
53    x: i32,
54    y: i32,
55    error: i32,
56    end_x: i32,
57    is_steep: bool,
58    y_step: i32,
59}
60
61impl BresenhamLineIter {
62    fn new(start: Vector2<i32>, end: Vector2<i32>) -> BresenhamLineIter {
63        let (mut x0, mut y0) = (start.x, start.y);
64        let (mut x1, mut y1) = (end.x, end.y);
65
66        let is_steep = (y1 - y0).abs() > (x1 - x0).abs();
67        if is_steep {
68            std::mem::swap(&mut x0, &mut y0);
69            std::mem::swap(&mut x1, &mut y1);
70        }
71
72        if x0 > x1 {
73            std::mem::swap(&mut x0, &mut x1);
74            std::mem::swap(&mut y0, &mut y1);
75        }
76
77        let dx = x1 - x0;
78
79        BresenhamLineIter {
80            dx,
81            dy: (y1 - y0).abs(),
82            x: x0,
83            y: y0,
84            error: dx / 2,
85            end_x: x1,
86            is_steep,
87            y_step: if y0 < y1 { 1 } else { -1 },
88        }
89    }
90}
91
92impl Iterator for BresenhamLineIter {
93    type Item = Vector2<i32>;
94
95    fn next(&mut self) -> Option<Vector2<i32>> {
96        if self.x > self.end_x {
97            None
98        } else {
99            let ret = if self.is_steep {
100                Vector2::new(self.y, self.x)
101            } else {
102                Vector2::new(self.x, self.y)
103            };
104
105            self.x += 1;
106            self.error -= self.dy;
107            if self.error < 0 {
108                self.y += self.y_step;
109                self.error += self.dx;
110            }
111
112            Some(ret)
113        }
114    }
115}
116
117/// This represents a change to some pages of a tile set, without specifying which tile set.
118#[derive(Clone, Debug, Default)]
119pub struct TileSetUpdate(FxHashMap<TileDefinitionHandle, TileDataUpdate>);
120
121impl Deref for TileSetUpdate {
122    type Target = FxHashMap<TileDefinitionHandle, TileDataUpdate>;
123    fn deref(&self) -> &Self::Target {
124        &self.0
125    }
126}
127impl DerefMut for TileSetUpdate {
128    fn deref_mut(&mut self) -> &mut Self::Target {
129        &mut self.0
130    }
131}
132
133/// A change of material for some tile. Either the material is being erased,
134/// or it is being replaced by the given material.
135#[derive(Debug, Clone)]
136pub enum MaterialUpdate {
137    /// This update is eliminating the material from the tile.
138    Erase,
139    /// This update is replacing the material of the tile.
140    Replace(TileMaterialBounds),
141}
142
143/// This represents a change to a tile in some tile set.
144#[derive(Clone, Debug, Default)]
145pub enum TileDataUpdate {
146    /// Remove this tile.
147    #[default]
148    Erase,
149    /// Make no change to the tile.
150    DoNothing,
151    /// This variant is for changing a material page tile.
152    MaterialTile(TileData),
153    /// This variant is for changing a freeform page tile.
154    FreeformTile(TileDefinition),
155    /// This variant is for changing the transform of a tile.
156    /// This update must be applied to some cell of transform set page.
157    /// It contains the new source tile for the transform cell.
158    TransformSet(Option<TileDefinitionHandle>),
159    /// This variant is for changing a tile's color.
160    Color(Color),
161    /// This variant is for changing a tile's property.
162    Property(Uuid, Option<TileSetPropertyValue>),
163    /// This variant is for changing some of a tile property's nine slices.
164    PropertySlice(Uuid, [Option<i8>; 9]),
165    /// This variant is for changing a tile's collider.
166    Collider(FxHashMap<Uuid, TileCollider>),
167    /// This variant is for changing a tile's material.
168    Material(TileMaterialBounds),
169}
170
171impl TileDataUpdate {
172    /// Use this update to create a new property value based on the given property id and value.
173    pub fn apply_to_property_value(
174        &self,
175        property_id: Uuid,
176        value: TileSetPropertyValue,
177    ) -> TileSetPropertyValue {
178        match self {
179            TileDataUpdate::Erase => value.make_default(),
180            TileDataUpdate::DoNothing => value,
181            TileDataUpdate::MaterialTile(tile_data) => tile_data
182                .properties
183                .get(&property_id)
184                .cloned()
185                .unwrap_or(value.make_default()),
186            TileDataUpdate::FreeformTile(tile_definition) => tile_definition
187                .data
188                .properties
189                .get(&property_id)
190                .cloned()
191                .unwrap_or(value.make_default()),
192            TileDataUpdate::TransformSet(_) => value,
193            TileDataUpdate::Color(_) => value,
194            TileDataUpdate::Property(uuid, new_value) => {
195                if *uuid == property_id {
196                    new_value.as_ref().cloned().unwrap_or(value.make_default())
197                } else {
198                    value
199                }
200            }
201            TileDataUpdate::PropertySlice(uuid, data) => match value {
202                TileSetPropertyValue::NineSlice(mut old_data) if property_id == *uuid => {
203                    for (i, v) in data.iter().enumerate() {
204                        old_data.0[i] = v.unwrap_or(old_data.0[i]);
205                    }
206                    TileSetPropertyValue::NineSlice(old_data)
207                }
208                _ if property_id == *uuid => {
209                    TileSetPropertyValue::NineSlice(NineI8(data.map(|x| x.unwrap_or_default())))
210                }
211                _ => value,
212            },
213            TileDataUpdate::Collider(_) => value,
214            TileDataUpdate::Material(_) => value,
215        }
216    }
217    /// The tile collider for the given id, if the collider is being replaced by this update.
218    /// None if the collider is not changed by this update.
219    pub fn get_tile_collider(&self, uuid: &Uuid) -> Option<&TileCollider> {
220        match self {
221            TileDataUpdate::Erase => Some(&TileCollider::None),
222            TileDataUpdate::MaterialTile(data) => {
223                data.colliders.get(uuid).or(Some(&TileCollider::None))
224            }
225            TileDataUpdate::FreeformTile(def) => {
226                def.data.colliders.get(uuid).or(Some(&TileCollider::None))
227            }
228            TileDataUpdate::Collider(map) => map.get(uuid),
229            _ => None,
230        }
231    }
232    /// The handle that should be used in place of the given handle, if this update has changed
233    /// the handle of a transform set tile.
234    /// None is returned if no tile should be rendered.
235    /// The given tile is returned if no change should be made.
236    pub fn substitute_transform_handle(
237        &self,
238        source: TileDefinitionHandle,
239    ) -> Option<TileDefinitionHandle> {
240        if let TileDataUpdate::TransformSet(new_source) = self {
241            *new_source
242        } else {
243            Some(source)
244        }
245    }
246    /// The render data that should be used in place of the given render data, based on this update.
247    /// None is returned if no tile should be rendered.
248    pub fn modify_render<'a>(&self, source: &'a TileRenderData) -> Option<Cow<'a, TileRenderData>> {
249        match self {
250            TileDataUpdate::Erase => None,
251            TileDataUpdate::MaterialTile(tile_data) => Some(Cow::Owned(TileRenderData {
252                material_bounds: source.material_bounds.clone(),
253                color: tile_data.color,
254            })),
255            TileDataUpdate::FreeformTile(def) => Some(Cow::Owned(TileRenderData {
256                material_bounds: Some(def.material_bounds.clone()),
257                color: def.data.color,
258            })),
259            TileDataUpdate::Color(color) => Some(Cow::Owned(TileRenderData {
260                material_bounds: source.material_bounds.clone(),
261                color: *color,
262            })),
263            TileDataUpdate::Material(material_bounds) => Some(Cow::Owned(TileRenderData {
264                material_bounds: Some(material_bounds.clone()),
265                color: source.color,
266            })),
267            _ => Some(Cow::Borrowed(source)),
268        }
269    }
270    /// Remove `TileData` and turn this object into `Erase`, if this is a MaterialTile. Otherwise, panic.
271    pub fn take_data(&mut self) -> TileData {
272        match std::mem::take(self) {
273            TileDataUpdate::MaterialTile(d) => d,
274            _ => panic!(),
275        }
276    }
277    /// Remove `TileDefinition` and turn this object into `Erase`, if this is a FreeformTile. Otherwise, panic.
278    pub fn take_definition(&mut self) -> TileDefinition {
279        match std::mem::take(self) {
280            TileDataUpdate::FreeformTile(d) => d,
281            _ => panic!(),
282        }
283    }
284    /// Swap whatever value is in this tile update with the corresponding value in the given TileData.
285    /// If this update has no data to swap, then do nothing and set this update to `DoNothing`.
286    pub fn swap_with_data(&mut self, data: &mut TileData) {
287        match self {
288            TileDataUpdate::DoNothing => (),
289            TileDataUpdate::Erase => {
290                Log::err("Tile data swap error");
291                *self = Self::DoNothing;
292            }
293            TileDataUpdate::MaterialTile(tile_data) => std::mem::swap(tile_data, data),
294            TileDataUpdate::FreeformTile(tile_definition) => {
295                std::mem::swap(&mut tile_definition.data, data)
296            }
297            TileDataUpdate::Color(color) => std::mem::swap(color, &mut data.color),
298            TileDataUpdate::Collider(colliders) => {
299                for (uuid, value) in colliders.iter_mut() {
300                    match data.colliders.entry(*uuid) {
301                        Entry::Occupied(mut e) => {
302                            if let TileCollider::None = value {
303                                *value = e.remove();
304                            } else {
305                                std::mem::swap(e.get_mut(), value)
306                            }
307                        }
308                        Entry::Vacant(e) => {
309                            e.insert(value.clone());
310                            *value = TileCollider::None;
311                        }
312                    }
313                }
314            }
315            TileDataUpdate::Property(uuid, value) => {
316                swap_hash_map_entry(data.properties.entry(*uuid), value)
317            }
318            TileDataUpdate::PropertySlice(uuid, value) => match data.properties.entry(*uuid) {
319                Entry::Occupied(mut e) => {
320                    if let TileSetPropertyValue::NineSlice(v0) = e.get_mut() {
321                        for (v0, v1) in v0.0.iter_mut().zip(value.iter_mut()) {
322                            if let Some(v1) = v1 {
323                                std::mem::swap(v0, v1);
324                            }
325                        }
326                    }
327                }
328                Entry::Vacant(e) => {
329                    let _ = e.insert(TileSetPropertyValue::NineSlice(NineI8(
330                        value.map(|v| v.unwrap_or_default()),
331                    )));
332                    *self = TileDataUpdate::Property(*uuid, None);
333                }
334            },
335            TileDataUpdate::TransformSet(_) => {
336                Log::err("Tile data swap error");
337                *self = Self::DoNothing;
338            }
339            TileDataUpdate::Material(_) => {
340                Log::err("Tile data swap error");
341                *self = Self::DoNothing;
342            }
343        }
344    }
345}
346
347impl TileSetUpdate {
348    /// Attempt to fill this TileSetUpdate based upon a TransTilesUpdate.
349    /// The TransTilesUpdate contains only positions, transformations, and TileDefinitionHandles for the tiles that are to be written.
350    /// In order to construct a TileSetUpdate, we use the given TileSet to copy tile bounds and tile definition data
351    /// as appropriate for the kind of page we are updating.
352    ///
353    /// Nothing is done if the given page does not exist or if it is a Material page that cannot be written to.
354    pub fn convert(
355        &mut self,
356        tiles: &TransTilesUpdate,
357        tile_set: &TileSetResource,
358        page: Vector2<i32>,
359        source_set: &TileSetResource,
360    ) {
361        let tile_set = tile_set.data_ref();
362        let Some(page_object) = tile_set.get_page(page) else {
363            return;
364        };
365        match &page_object.source {
366            TileSetPageSource::Atlas(_) => self.convert_material(tiles, page),
367            TileSetPageSource::Freeform(_) => {
368                drop(tile_set);
369                self.convert_freeform(tiles, &TileSetRef::new(source_set).as_loaded(), page);
370            }
371            TileSetPageSource::Transform(_) | TileSetPageSource::Animation(_) => {
372                drop(tile_set);
373                self.convert_transform(tiles, &TileSetRef::new(source_set).as_loaded(), page);
374            }
375        }
376    }
377    fn convert_material(&mut self, tiles: &TransTilesUpdate, page: Vector2<i32>) {
378        for (pos, value) in tiles.iter() {
379            let Some(handle) = TileDefinitionHandle::try_new(page, *pos) else {
380                continue;
381            };
382            if value.is_some() {
383                self.insert(handle, TileDataUpdate::MaterialTile(TileData::default()));
384            } else {
385                self.insert(handle, TileDataUpdate::Erase);
386            }
387        }
388    }
389    fn convert_freeform(
390        &mut self,
391        tiles: &TransTilesUpdate,
392        tile_set: &OptionTileSet,
393        page: Vector2<i32>,
394    ) {
395        for (pos, value) in tiles.iter() {
396            let Some(handle) = TileDefinitionHandle::try_new(page, *pos) else {
397                continue;
398            };
399            if let Some(def) = value.and_then(|(t, h)| tile_set.get_transformed_definition(t, h)) {
400                self.insert(handle, TileDataUpdate::FreeformTile(def));
401            } else {
402                self.insert(handle, TileDataUpdate::Erase);
403            }
404        }
405    }
406    fn convert_transform(
407        &mut self,
408        tiles: &TransTilesUpdate,
409        tile_set: &OptionTileSet,
410        page: Vector2<i32>,
411    ) {
412        for (pos, value) in tiles.iter() {
413            let Some(target_handle) = TileDefinitionHandle::try_new(page, *pos) else {
414                continue;
415            };
416            if let Some((trans, handle)) = value {
417                let handle = tile_set
418                    .get_transformed_version(*trans, *handle)
419                    .unwrap_or(*handle);
420                self.insert(target_handle, TileDataUpdate::TransformSet(Some(handle)));
421            } else {
422                self.insert(target_handle, TileDataUpdate::TransformSet(None));
423            }
424        }
425    }
426    /// Get the color being set onto the given tile by this update, if a color is being set.
427    pub fn get_color(&self, page: Vector2<i32>, position: Vector2<i32>) -> Option<Color> {
428        let handle = TileDefinitionHandle::try_new(page, position)?;
429        match self.get(&handle)? {
430            TileDataUpdate::Erase => Some(Color::default()),
431            TileDataUpdate::MaterialTile(data) => Some(data.color),
432            TileDataUpdate::FreeformTile(def) => Some(def.data.color),
433            TileDataUpdate::Color(color) => Some(*color),
434            _ => None,
435        }
436    }
437    /// Get the material being set onto the given tile by this update, if a material is being set.
438    pub fn get_material(
439        &self,
440        page: Vector2<i32>,
441        position: Vector2<i32>,
442    ) -> Option<MaterialUpdate> {
443        let handle = TileDefinitionHandle::try_new(page, position)?;
444        match self.get(&handle)? {
445            TileDataUpdate::Erase => Some(MaterialUpdate::Erase),
446            TileDataUpdate::FreeformTile(def) => {
447                Some(MaterialUpdate::Replace(def.material_bounds.clone()))
448            }
449            TileDataUpdate::Material(mat) => Some(MaterialUpdate::Replace(mat.clone())),
450            _ => None,
451        }
452    }
453    /// Get the tile bounds being set onto the given tile by this update, if possible.
454    pub fn get_tile_bounds(
455        &self,
456        page: Vector2<i32>,
457        position: Vector2<i32>,
458    ) -> Option<TileBounds> {
459        let handle = TileDefinitionHandle::try_new(page, position)?;
460        match self.get(&handle)? {
461            TileDataUpdate::Erase => Some(TileBounds::default()),
462            TileDataUpdate::FreeformTile(def) => Some(def.material_bounds.bounds.clone()),
463            TileDataUpdate::Material(mat) => Some(mat.bounds.clone()),
464            _ => None,
465        }
466    }
467    /// Get the value of the given property being set onto the given tile by this update, if possible.
468    pub fn get_property(
469        &self,
470        page: Vector2<i32>,
471        position: Vector2<i32>,
472        property_id: Uuid,
473    ) -> Option<Option<TileSetPropertyValue>> {
474        let handle = TileDefinitionHandle::try_new(page, position)?;
475        match self.get(&handle)? {
476            TileDataUpdate::Erase => Some(None),
477            TileDataUpdate::MaterialTile(data) => Some(data.properties.get(&property_id).cloned()),
478            TileDataUpdate::FreeformTile(def) => {
479                Some(def.data.properties.get(&property_id).cloned())
480            }
481            TileDataUpdate::Property(id, value) if *id == property_id => Some(value.clone()),
482            _ => None,
483        }
484    }
485    /// Get the value of the given collider being set onto the given tile by this update, if possible.
486    pub fn get_collider(
487        &self,
488        page: Vector2<i32>,
489        position: Vector2<i32>,
490        collider_id: &Uuid,
491    ) -> Option<&TileCollider> {
492        let handle = TileDefinitionHandle::try_new(page, position)?;
493        self.get(&handle)?.get_tile_collider(collider_id)
494    }
495    /// Set the given color on the given tile.
496    pub fn set_color(&mut self, page: Vector2<i32>, position: Vector2<i32>, color: Color) {
497        if let Some(handle) = TileDefinitionHandle::try_new(page, position) {
498            self.insert(handle, TileDataUpdate::Color(color));
499        }
500    }
501    /// Set the given property value on the given tile.
502    pub fn set_property(
503        &mut self,
504        page: Vector2<i32>,
505        position: Vector2<i32>,
506        property_id: Uuid,
507        value: Option<TileSetPropertyValue>,
508    ) {
509        if let Some(handle) = TileDefinitionHandle::try_new(page, position) {
510            self.insert(handle, TileDataUpdate::Property(property_id, value));
511        }
512    }
513    /// Set the given value to the given slice of the given property of the given tile.
514    pub fn set_property_slice(
515        &mut self,
516        page: Vector2<i32>,
517        position: Vector2<i32>,
518        subposition: Vector2<usize>,
519        property_id: Uuid,
520        value: i8,
521    ) {
522        use TileSetPropertyValue as PropValue;
523        let index = TileSetPropertyValue::nine_position_to_index(subposition);
524        if let Some(handle) = TileDefinitionHandle::try_new(page, position) {
525            match self.entry(handle) {
526                Entry::Occupied(mut e) => match e.get_mut() {
527                    TileDataUpdate::PropertySlice(uuid, d0) if *uuid == property_id => {
528                        d0[index] = Some(value);
529                    }
530                    TileDataUpdate::Property(uuid, Some(PropValue::NineSlice(d0)))
531                        if *uuid == property_id =>
532                    {
533                        d0.0[index] = value;
534                    }
535                    d0 => {
536                        let mut data = [0; 9];
537                        data[index] = value;
538                        *d0 = TileDataUpdate::Property(
539                            property_id,
540                            Some(PropValue::NineSlice(NineI8(data))),
541                        );
542                    }
543                },
544                Entry::Vacant(e) => {
545                    let mut data = [None; 9];
546                    data[index] = Some(value);
547                    let _ = e.insert(TileDataUpdate::PropertySlice(property_id, data));
548                }
549            }
550        }
551    }
552    /// Set the given property value on the given tile.
553    pub fn set_collider<I: Iterator<Item = Uuid>>(
554        &mut self,
555        page: Vector2<i32>,
556        position: Vector2<i32>,
557        property_ids: I,
558        value: &TileCollider,
559    ) {
560        let Some(handle) = TileDefinitionHandle::try_new(page, position) else {
561            return;
562        };
563        let mut colliders = FxHashMap::default();
564        colliders.extend(property_ids.map(|uuid| (uuid, value.clone())));
565        self.insert(handle, TileDataUpdate::Collider(colliders));
566    }
567    /// Set the given material on the given tile.
568    pub fn set_material(
569        &mut self,
570        page: Vector2<i32>,
571        position: Vector2<i32>,
572        value: TileMaterialBounds,
573    ) {
574        if let Some(handle) = TileDefinitionHandle::try_new(page, position) {
575            self.insert(handle, TileDataUpdate::Material(value));
576        }
577    }
578}
579
580type RotTileHandle = (OrthoTransformation, TileDefinitionHandle);
581
582/// This is a step in the process of performing an edit to a tile map, brush, or tile set.
583/// It provides handles for the tiles to be written and the transformation to apply to those
584/// tiles. A None indicates that the tile is to be erased.
585#[derive(Clone, Debug, Default)]
586pub struct TransTilesUpdate(TileGridMap<Option<RotTileHandle>>);
587
588/// A set of changes to a set of tiles. A value of None indicates that a tile
589/// is being removed from the set. A None indicates that the tile is to be erased.
590#[derive(Clone, Debug, Default, PartialEq)]
591pub struct TilesUpdate(TileGridMap<Option<TileDefinitionHandle>>);
592
593impl Deref for TilesUpdate {
594    type Target = TileGridMap<Option<TileDefinitionHandle>>;
595
596    fn deref(&self) -> &Self::Target {
597        &self.0
598    }
599}
600
601impl DerefMut for TilesUpdate {
602    fn deref_mut(&mut self) -> &mut Self::Target {
603        &mut self.0
604    }
605}
606
607impl Deref for TransTilesUpdate {
608    type Target = TileGridMap<Option<RotTileHandle>>;
609
610    fn deref(&self) -> &Self::Target {
611        &self.0
612    }
613}
614
615impl DerefMut for TransTilesUpdate {
616    fn deref_mut(&mut self) -> &mut Self::Target {
617        &mut self.0
618    }
619}
620
621impl TransTilesUpdate {
622    /// Construct a TilesUpdate by finding the transformed version of each tile
623    /// in the given tile set.
624    pub fn build_tiles_update(&self, tile_set: &OptionTileSet) -> TilesUpdate {
625        let mut result = TilesUpdate::default();
626        for (pos, value) in self.iter() {
627            if let Some((trans, handle)) = value {
628                let handle = tile_set
629                    .get_transformed_version(*trans, *handle)
630                    .unwrap_or(*handle);
631                result.insert(*pos, Some(handle));
632            } else {
633                result.insert(*pos, None);
634            }
635        }
636        result
637    }
638    /// Fills the given tiles at the given point using tiles from the given source. This method
639    /// extends tile map when trying to fill at a point that lies outside the bounding rectangle.
640    /// Keep in mind, that flood fill is only possible either on free cells or on cells with the same
641    /// tile kind. Modifications to the tile source are written into the given TileUpdates object
642    /// rather than modifying the tiles directly.
643    pub fn flood_fill<T: BoundedTileSource, S: TileSource>(
644        &mut self,
645        tiles: &T,
646        start_point: Vector2<i32>,
647        brush: &S,
648    ) {
649        let mut bounds = tiles.bounding_rect();
650        bounds.push(start_point);
651
652        let allowed_definition = tiles.get_at(start_point);
653        let mut stack = vec![start_point];
654        while let Some(position) = stack.pop() {
655            let definition = tiles.get_at(position);
656            if definition == allowed_definition && !self.contains_key(&position) {
657                let value = brush.get_at(position).map(|h| (brush.transformation(), h));
658                self.insert(position, value);
659
660                // Continue on neighbours.
661                for neighbour_position in [
662                    Vector2::new(position.x - 1, position.y),
663                    Vector2::new(position.x + 1, position.y),
664                    Vector2::new(position.x, position.y - 1),
665                    Vector2::new(position.x, position.y + 1),
666                ] {
667                    if bounds.contains(neighbour_position) {
668                        stack.push(neighbour_position);
669                    }
670                }
671            }
672        }
673    }
674    /// Draws the given tiles on the tile map
675    #[inline]
676    pub fn draw_tiles(&mut self, origin: Vector2<i32>, brush: &Stamp) {
677        let trans = brush.transformation();
678        for (local_position, handle) in brush.iter() {
679            self.insert(origin + local_position, Some((trans, *handle)));
680        }
681    }
682    /// Erases the tiles under the given brush.
683    #[inline]
684    pub fn erase_stamp(&mut self, origin: Vector2<i32>, brush: &Stamp) {
685        for local_position in brush.keys() {
686            self.insert(origin + local_position, None);
687        }
688    }
689    /// Erases the given tile.
690    pub fn erase(&mut self, position: Vector2<i32>) {
691        self.insert(position, None);
692    }
693    /// Fills the given rectangle using the given stamp.
694    pub fn rect_fill(&mut self, start: Vector2<i32>, end: Vector2<i32>, stamp: &Stamp) {
695        let region = TileRegion::from_points(start, end);
696        let stamp_source = stamp.repeat(start, end);
697        self.rect_fill_inner(region, &stamp_source);
698    }
699    /// Fills the given rectangle using random tiles from the given stamp.
700    pub fn rect_fill_random(&mut self, start: Vector2<i32>, end: Vector2<i32>, stamp: &Stamp) {
701        let region = TileRegion::from_points(start, end);
702        self.rect_fill_inner(region, &RandomTileSource(stamp));
703    }
704    /// Fills the given rectangle using the given tiles.
705    fn rect_fill_inner<S: TileSource>(&mut self, region: TileRegion, brush: &S) {
706        let trans = brush.transformation();
707        for (target, source) in region.iter() {
708            if let Some(definition_handle) = brush.get_at(source) {
709                self.insert(target, Some((trans, definition_handle)));
710            }
711        }
712    }
713    /// Draw a line from a point to point.
714    pub fn draw_line<S: TileSource>(&mut self, from: Vector2<i32>, to: Vector2<i32>, brush: &S) {
715        let trans = brush.transformation();
716        for position in BresenhamLineIter::new(from, to) {
717            if let Some(random_tile) = brush.get_at(position - from) {
718                self.insert(position, Some((trans, random_tile)));
719            }
720        }
721    }
722
723    /// Fills in a rectangle using special brush with 3x3 tiles. It puts
724    /// corner tiles in the respective corners of the target rectangle and draws lines between each
725    /// corner using middle tiles.
726    pub fn nine_slice(&mut self, start: Vector2<i32>, end: Vector2<i32>, brush: &Stamp) {
727        self.nine_slice_inner(
728            start,
729            end,
730            brush,
731            |update, target_region, source, source_region| {
732                update.rect_fill_inner(
733                    target_region,
734                    &RepeatTileSource {
735                        source,
736                        region: source_region,
737                    },
738                )
739            },
740        );
741    }
742    /// Fills in a rectangle using special brush with 3x3 tiles. It puts
743    /// corner tiles in the respective corners of the target rectangle and draws lines between each
744    /// corner using middle tiles shuffled into random order.
745    pub fn nine_slice_random(&mut self, start: Vector2<i32>, end: Vector2<i32>, brush: &Stamp) {
746        self.nine_slice_inner(
747            start,
748            end,
749            brush,
750            |update, target_region, source, source_region| {
751                update.rect_fill_inner(
752                    target_region,
753                    &PartialRandomTileSource(source, source_region.bounds),
754                )
755            },
756        );
757    }
758
759    /// Fills in a rectangle using special brush with 3x3 tiles. It puts
760    /// corner tiles in the respective corners of the target rectangle and draws lines between each
761    /// corner using middle tiles.
762    #[inline]
763    fn nine_slice_inner<F>(
764        &mut self,
765        start: Vector2<i32>,
766        end: Vector2<i32>,
767        stamp: &Stamp,
768        fill: F,
769    ) where
770        F: Fn(&mut TransTilesUpdate, TileRegion, &Stamp, TileRegion),
771    {
772        let Some(stamp_rect) = *stamp.bounding_rect() else {
773            return;
774        };
775        let rect = TileRect::from_points(start, end);
776        let region = TileRegion {
777            origin: start,
778            bounds: rect.into(),
779        };
780        let inner_region = region.clone().deflate(1, 1);
781
782        let stamp_region = TileRegion::from_bounds_and_direction(stamp_rect.into(), start - end);
783        let inner_stamp_region = stamp_region.clone().deflate(1, 1);
784
785        // Place corners first.
786        let trans = stamp.transformation();
787        for (corner_position, actual_corner_position) in [
788            (stamp_rect.left_top_corner(), rect.left_top_corner()),
789            (stamp_rect.right_top_corner(), rect.right_top_corner()),
790            (stamp_rect.right_bottom_corner(), rect.right_bottom_corner()),
791            (stamp_rect.left_bottom_corner(), rect.left_bottom_corner()),
792        ] {
793            if let Some(tile) = stamp.get(corner_position) {
794                self.insert(actual_corner_position, Some((trans, *tile)));
795            }
796        }
797
798        let top = region.clone().with_bounds(
799            TileRect::from_points(
800                rect.left_top_corner() + Vector2::new(1, 0),
801                rect.right_top_corner() + Vector2::new(-1, 0),
802            )
803            .into(),
804        );
805        let bottom = region.clone().with_bounds(
806            TileRect::from_points(
807                rect.left_bottom_corner() + Vector2::new(1, 0),
808                rect.right_bottom_corner() + Vector2::new(-1, 0),
809            )
810            .into(),
811        );
812        let left = region.clone().with_bounds(
813            TileRect::from_points(
814                rect.left_bottom_corner() + Vector2::new(0, 1),
815                rect.left_top_corner() + Vector2::new(0, -1),
816            )
817            .into(),
818        );
819        let right = region.clone().with_bounds(
820            TileRect::from_points(
821                rect.right_bottom_corner() + Vector2::new(0, 1),
822                rect.right_top_corner() + Vector2::new(0, -1),
823            )
824            .into(),
825        );
826        let stamp_top = stamp_region.clone().with_bounds(
827            TileRect::from_points(
828                stamp_rect.left_top_corner() + Vector2::new(1, 0),
829                stamp_rect.right_top_corner() + Vector2::new(-1, 0),
830            )
831            .into(),
832        );
833        let stamp_bottom = stamp_region.clone().with_bounds(
834            TileRect::from_points(
835                stamp_rect.left_bottom_corner() + Vector2::new(1, 0),
836                stamp_rect.right_bottom_corner() + Vector2::new(-1, 0),
837            )
838            .into(),
839        );
840        let stamp_left = stamp_region.clone().with_bounds(
841            TileRect::from_points(
842                stamp_rect.left_bottom_corner() + Vector2::new(0, 1),
843                stamp_rect.left_top_corner() + Vector2::new(0, -1),
844            )
845            .into(),
846        );
847        let stamp_right = stamp_region.clone().with_bounds(
848            TileRect::from_points(
849                stamp_rect.right_bottom_corner() + Vector2::new(0, 1),
850                stamp_rect.right_top_corner() + Vector2::new(0, -1),
851            )
852            .into(),
853        );
854
855        if rect.size.x > 2 && stamp_rect.size.x > 2 {
856            fill(self, top, stamp, stamp_top);
857            fill(self, bottom, stamp, stamp_bottom);
858        }
859        if rect.size.y > 2 && stamp_rect.size.y > 2 {
860            fill(self, left, stamp, stamp_left);
861            fill(self, right, stamp, stamp_right);
862        }
863        fill(self, inner_region, stamp, inner_stamp_region);
864    }
865}