Skip to main content

fyrox_impl/scene/tilemap/
autotile.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
21use std::fmt::Debug;
22
23use super::*;
24use fxhash::FxHashMap;
25use fyrox_autotile::{
26    AutoPatternConstraint, AutoPatternValueMap, AutoTerrainPatternMap, AutoTileContext, AutoTiler,
27    HashConstraintMap, HashWfcConstraint, OffsetPosition, PatternSource, TileConstraint,
28    Vector2Offset, WfcConstrain, WfcFailure, WfcPropagator,
29};
30use fyrox_core::log::Log;
31
32use crate::core::rand::Rng;
33pub use fyrox_autotile::PatternBits;
34
35impl From<NineI8> for PatternBits {
36    fn from(value: NineI8) -> Self {
37        Self(value.0)
38    }
39}
40
41/// A value that specifies the terrain of a tile map cell for auto-tiling.
42/// This value comes from the center of a nine-slice tile set property,
43/// so it can constrain which tiles are permitted in a particular cell.
44pub type TileTerrainId = i8;
45
46/// Autotiler for the tiles of a [`TileSet`] that uses the nine-slice
47/// values of one of the tile set's properties as the auto-tiler's pattern.
48#[derive(Debug, Default, Clone)]
49pub struct TileSetAutoTiler(AutoTiler<Vector2<i32>, PatternBits>);
50/// This is a specialization of [`AutoTileContext`] for use with a [`TileSet`].
51/// The pattern of each tile is taken from the values of a nine-slice property,
52/// and [`TileDefinitionHandle`] is used to represent individual tiles.
53///
54/// See [`TileSetAutoTileContext::fill_pattern_map`] for details on how to fill a
55/// `TileSetAutoTileContext` with data from a tile set in preparation for auto-tiling.
56#[derive(Default, Clone)]
57pub struct TileSetAutoTileContext(
58    AutoTileContext<TileTerrainId, PatternBits, TileDefinitionHandle>,
59);
60
61impl Debug for TileSetAutoTileContext {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        self.0.fmt(f)
64    }
65}
66
67/// A map from `Vector2<i32>` positions to auto-tile cell constriants.
68/// Each cell contraint is either an [`TileTerrainId`] which represents the center of the
69/// permitted patterns for this cell, or else it is a [`PatternBits`] which
70/// represents the only permitted pattern for this cell and thereby constrains
71/// the neighboring cells.
72pub type TileSetConstraintMap = HashConstraintMap<Vector2<i32>, TileTerrainId, PatternBits>;
73/// A terrain pattern map for tile map auto-tiler, using the fields of a
74/// nine-slice tile set property for the terrain types and the patterns.
75pub type TileSetTerrainPatternMap = AutoTerrainPatternMap<TileTerrainId, PatternBits>;
76/// A mapping from auto-tiler patterns to tile handles. Multiple tiles may share the
77/// same pattern, and each tile is given a frequency value that controls the probability
78/// of randomly selecting that tile when its pattern is chosen.
79pub type TileSetAutoValueMap = AutoPatternValueMap<PatternBits, TileDefinitionHandle>;
80/// A pair of a [`TileSetConstraintMap`] and a [`TileSetTerrainPatternMap`].
81/// - The constraint map tells the auto-tiler which cells have undecided patterns,
82///   which cells have fixed patterns, and the terrain type of the undecided cells.
83/// - The terrain map tells the auto-tiler which patterns are available for each
84///   terrain type, and the order in which the patterns should be tried.
85pub type TileSetAutoTileConstraint<'a, 'b> =
86    AutoPatternConstraint<'a, 'b, Vector2<i32>, TileTerrainId, PatternBits>;
87
88/// A hash table based wave function collapse constraint that maps [`PatternBits`]
89/// objects to sets of [`TileDefinitionHandle`]. Each handle refers to a tile,
90/// and each tile is assigned a pattern and a frequency. The sum of the frequencies
91/// of all a pattern's tiles becomes the frequency of the pattern, which is then
92/// normalized into a probability between 0.0 and 1.0 for use as a constraint
93/// for a [`TileSetWfcPropagator`] which will perform the wave function collapse.
94#[derive(Default)]
95pub struct TileSetWfcConstraint(HashWfcConstraint<PatternBits, TileDefinitionHandle>);
96
97impl Deref for TileSetWfcConstraint {
98    type Target = HashWfcConstraint<PatternBits, TileDefinitionHandle>;
99
100    fn deref(&self) -> &Self::Target {
101        &self.0
102    }
103}
104
105impl DerefMut for TileSetWfcConstraint {
106    fn deref_mut(&mut self) -> &mut Self::Target {
107        &mut self.0
108    }
109}
110
111impl TileSetWfcConstraint {
112    /// Clear the data from this constraint and replace it with new data
113    /// based upon the given tile set and property UUIDs. All of the tiles
114    /// of the tile set are searched and each one's pattern property is checked
115    /// for a center value that matches one of the keys of `terrain_freq`.
116    /// When such a tile is found, it is inserted into the map using the pattern property
117    /// value as the pattern and the frequency property value as the frequency.
118    /// - `tile_set`: The tile set to iterate through.
119    /// - `pattern_property`: The UUID of a nine-slice property in `tile_set` that will
120    ///   be used for the pattern of each tile. The center value of the nine-slice is
121    ///   the terrain, unless the center value is 0, in which case the tile is ignored.
122    /// - `frequency_property`: The UUID of a float property in `tile_set` that will be
123    ///   used for the frequency of each tile. If None, then every tile has frequency 1.0.
124    /// - `terrain_freq`: A hash map of the terrains that will be used in wave function collapse.
125    ///   Tiles whose center value are not keys in this hash map will be ignored.
126    ///   Tiles whose center value are keys in this hash map will have their frequency
127    ///   multiplied by the corresponding value in the hash map to calculate the final
128    ///   frequency of the tile. This allows terrains to have their frequency weighted,
129    ///   and allows unwanted terrains to be excluded.
130    ///
131    /// *Note:* Terrain 0 is treated specially. It has one pattern, the all-zeros default
132    /// [`PatternBits`], and it corresponds to the empty tile. Tiles whose pattern value
133    /// have 0 in the center are presumed to be not intended to be part of the wave
134    /// function collapse, since 0 is the default value. The frequency of choosing empty
135    /// tiles in wave function collapse can be controlled by setting `terrain_freq[0]` to
136    /// some value.
137    pub fn fill_pattern_map(
138        &mut self,
139        tile_set: &TileSet,
140        pattern_property: TileSetPropertyNine,
141        frequency_property: Option<TileSetPropertyF32>,
142        terrain_freq: &FxHashMap<TileTerrainId, f32>,
143    ) -> Result<(), FillPatternMapError> {
144        self.clear();
145        if let Some(&frequency) = terrain_freq.get(&0) {
146            self.add(
147                PatternBits::default(),
148                frequency,
149                TileDefinitionHandle::EMPTY,
150            );
151        }
152        if tile_set
153            .find_property(*pattern_property.property_uuid())
154            .is_none()
155        {
156            return Err(FillPatternMapError::PatternInvalidId);
157        }
158        if let Some(id) = frequency_property {
159            if tile_set.find_property(*id.property_uuid()).is_none() {
160                return Err(FillPatternMapError::FrequencyInvalidId);
161            }
162        }
163        for handle in tile_set.all_tiles() {
164            let frequency = if let Some(id) = frequency_property {
165                id.get_from_tile_set(tile_set, handle)
166                    .map_err(|_| FillPatternMapError::FrequencyWrongType)?
167            } else {
168                1.0
169            };
170            if frequency <= 0.0 {
171                continue;
172            }
173            let pattern: NineI8 = pattern_property
174                .get_from_tile_set(tile_set, handle)
175                .map_err(|_| FillPatternMapError::PatternWrongType)?;
176            let pattern: PatternBits = pattern.into();
177            let center = pattern.center();
178            if center != 0 {
179                if let Some(terrain_frequency) = terrain_freq.get(&center) {
180                    self.add(pattern, frequency * terrain_frequency, handle);
181                }
182            }
183        }
184        self.finalize_with_terrain_normalization(PatternBits::center);
185        Ok(())
186    }
187}
188
189/// An error that might occur while filling a pattern map from a tile set
190/// using [`TileSetAutoTileContext::fill_pattern_map`].
191#[derive(Debug, PartialEq, Eq)]
192pub enum FillPatternMapError {
193    /// The UUID for the frequency property was not found in the tile set.
194    FrequencyInvalidId,
195    /// The frequency property was not f32.
196    FrequencyWrongType,
197    /// The UUID for the terrain property was not found in the tile set.
198    PatternInvalidId,
199    /// The terrain property was not a nine-slice.
200    PatternWrongType,
201}
202
203impl Error for FillPatternMapError {}
204
205impl Display for FillPatternMapError {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        match self {
208            FillPatternMapError::FrequencyInvalidId => write!(
209                f,
210                "The property UUID for the frequency does not match any property in the tile set."
211            ),
212            FillPatternMapError::FrequencyWrongType => {
213                write!(f, "The frequency property should be an f32.")
214            }
215            FillPatternMapError::PatternInvalidId => write!(
216                f,
217                "The property UUID for the pattern does not match any property in the tile set."
218            ),
219            FillPatternMapError::PatternWrongType => {
220                write!(f, "The pattern property should be a nine-slice property.")
221            }
222        }
223    }
224}
225
226impl Deref for TileSetAutoTileContext {
227    type Target = AutoTileContext<TileTerrainId, PatternBits, TileDefinitionHandle>;
228
229    fn deref(&self) -> &Self::Target {
230        &self.0
231    }
232}
233
234impl DerefMut for TileSetAutoTileContext {
235    fn deref_mut(&mut self) -> &mut Self::Target {
236        &mut self.0
237    }
238}
239
240impl TileSetAutoTileContext {
241    /// Prepare for auto-tiling by extracting tile patterns from the given tile set.
242    /// Each tile has an associated pattern through the value of the property
243    /// with the UUID given by `pattern_property`. Each pattern contains nine i8 values
244    /// arranged in a 3x3 grid. The outer eight values are used to determine which tiles
245    /// are permitted to be adjacent to which other tiles, while the central i8 controls
246    /// which cells may have this tile. The central value of 0 is reserved for cells
247    /// that should be empty, and any tiles which have a central value of 0 are ignored
248    /// by the auto-tiler.
249    ///
250    /// When more than one tile has the same pattern, the auto-tiler must choose between
251    /// the tiles randomly. The `frequency_property` is used to weigh the probabilities
252    /// of choosing each tile, so tiles with higher values are more likely to be chosen.
253    /// If no frequency property is given, then all tiles are equally likely.
254    pub fn fill_pattern_map(
255        &mut self,
256        tile_set: &TileSet,
257        pattern_property: TileSetPropertyNine,
258        frequency_property: Option<TileSetPropertyF32>,
259    ) -> Result<(), FillPatternMapError> {
260        self.clear();
261        self.add(0, PatternBits::default(), 1.0, TileDefinitionHandle::EMPTY);
262        if tile_set
263            .find_property(*pattern_property.property_uuid())
264            .is_none()
265        {
266            return Err(FillPatternMapError::PatternInvalidId);
267        }
268        if let Some(id) = frequency_property {
269            if tile_set.find_property(*id.property_uuid()).is_none() {
270                return Err(FillPatternMapError::FrequencyInvalidId);
271            }
272        }
273        for handle in tile_set.all_tiles() {
274            let frequency = if let Some(id) = frequency_property {
275                id.get_from_tile_set(tile_set, handle)
276                    .map_err(|_| FillPatternMapError::FrequencyWrongType)?
277            } else {
278                1.0
279            };
280            if frequency <= 0.0 {
281                continue;
282            }
283            let pattern: NineI8 = pattern_property
284                .get_from_tile_set(tile_set, handle)
285                .map_err(|_| FillPatternMapError::PatternWrongType)?;
286            let pattern: PatternBits = pattern.into();
287            let center = pattern.center();
288            if center != 0 {
289                self.add(center, pattern, frequency, handle);
290            }
291        }
292        self.sort();
293        Ok(())
294    }
295}
296
297impl Deref for TileSetAutoTiler {
298    type Target = AutoTiler<Vector2<i32>, PatternBits>;
299
300    fn deref(&self) -> &Self::Target {
301        &self.0
302    }
303}
304
305impl DerefMut for TileSetAutoTiler {
306    fn deref_mut(&mut self) -> &mut Self::Target {
307        &mut self.0
308    }
309}
310
311impl TileSetAutoTiler {
312    /// Modify the given tile map update based on the result of the
313    /// auto-tiler.
314    pub fn apply_autotile_to_update<R: Rng + ?Sized>(
315        &self,
316        rng: &mut R,
317        value_map: &TileSetAutoValueMap,
318        update: &mut MacroTilesUpdate,
319    ) {
320        for (pos, pat) in self.iter() {
321            let Some(handle) = value_map
322                .get(pat)
323                .and_then(|ps| ps.get_random(rng))
324                .cloned()
325            else {
326                continue;
327            };
328            let source = update.get(pos).cloned().flatten().and_then(|el| el.source);
329            let handle = if handle.is_empty() {
330                None
331            } else {
332                Some(StampElement { handle, source })
333            };
334            _ = update.insert(*pos, handle);
335        }
336    }
337}
338
339/// An object that wraps a tile map, a tile set, an update, and the UUID of the property
340/// that is used to store the pattern of each tile, and then provides access to the pattern
341/// at any cell. It looks for the cell's handle in the update, then in the tile map, and
342/// then uses the tile set to find the pattern.
343pub struct TileSetPatternSource<'a, 'b, 'c> {
344    /// The tile map to get tile handles from.
345    pub tile_map: &'a TileMap,
346    /// The tile set to get property values from.
347    pub tile_set: &'b TileSet,
348    /// The current set of modifications to the tile map.
349    /// When a cell has been modified, the updated handle will be used
350    /// instead of the handle stored in `tile_map`.
351    pub update: &'c MacroTilesUpdate,
352    /// The UUID of the property to use as the tile pattern.
353    pub property_id: TileSetPropertyNine,
354}
355
356impl TileSetPatternSource<'_, '_, '_> {
357    fn pattern_at(&self, position: &Vector2<i32>) -> Result<PatternBits, TilePropertyError> {
358        let Some(element) = self
359            .update
360            .get(position)
361            .cloned()
362            .unwrap_or_else(|| self.tile_map.tile_handle(*position).map(|h| h.into()))
363        else {
364            return Ok(PatternBits::default());
365        };
366        self.property_id
367            .get_from_tile_set(self.tile_set, element.handle)
368            .map(|v| v.into())
369    }
370}
371
372impl PatternSource for TileSetPatternSource<'_, '_, '_> {
373    type Position = Vector2<i32>;
374    type Terrain = TileTerrainId;
375    type Pattern = PatternBits;
376
377    fn get_terrain(&self, position: &Vector2<i32>) -> Option<TileTerrainId> {
378        match self.pattern_at(position) {
379            Ok(pattern) => Some(pattern.center()),
380            Err(err) => {
381                Log::err(err.to_string());
382                None
383            }
384        }
385    }
386    /// The constraint for the cell at the given position.
387    fn get(&self, position: &Vector2<i32>) -> TileConstraint<TileTerrainId, PatternBits> {
388        match self.pattern_at(position) {
389            Ok(pattern) => TileConstraint::Pattern(pattern),
390            Err(err) => {
391                Log::err(err.to_string());
392                TileConstraint::None
393            }
394        }
395    }
396}
397
398/// Wave function collapse propagator for the tiles of a [`TileSet`] that uses
399/// the nine-slice values of one of the tile set's properties as the wave function's
400/// pattern.
401#[derive(Debug, Default, Clone)]
402pub struct TileSetWfcPropagator {
403    propagator: WfcPropagator<Vector2<i32>, PatternBits>,
404    edge_restrictions: FxHashMap<Vector2<i32>, PatternBits>,
405}
406
407impl Deref for TileSetWfcPropagator {
408    type Target = WfcPropagator<Vector2<i32>, PatternBits>;
409
410    fn deref(&self) -> &Self::Target {
411        &self.propagator
412    }
413}
414
415impl DerefMut for TileSetWfcPropagator {
416    fn deref_mut(&mut self) -> &mut Self::Target {
417        &mut self.propagator
418    }
419}
420
421impl TileSetWfcPropagator {
422    /// After all the wave cells have been added using [`WfcPropagator::add_cell`],
423    /// this method may be used to automatically restrict the edges of the cells by
424    /// using the given tile map to find the patterns of surrounding tiles and
425    /// calling [`WfcPropagator::restrict_edge`] as appropriate.
426    ///
427    /// This forces the wave function collapse to fit smoothly with the existing
428    /// tiles of the tile map, though such restrictions may make failure more likely.
429    /// [`WfcFailure`] is returned if wave function collapse is made impossible by the
430    /// restrictions.
431    pub fn constrain_edges<Con>(
432        &mut self,
433        tile_set: &TileSet,
434        pattern_property: TileSetPropertyNine,
435        tile_map: &TileMap,
436        update: &MacroTilesUpdate,
437        constraint: &Con,
438    ) -> Result<(), WfcFailure>
439    where
440        Con: WfcConstrain<Pattern = PatternBits, Offset = Vector2Offset>,
441    {
442        let tiles = tile_map.tiles();
443        let tiles = tiles.map(|r| r.data_ref());
444        let mut edge_restrictions = std::mem::take(&mut self.edge_restrictions);
445        edge_restrictions.clear();
446        for &p in self.positions() {
447            for offset in Vector2::all_offsets() {
448                let p = p + offset;
449                if self.edge_restrictions.contains_key(&p) {
450                    continue;
451                }
452                if self.contains_cell(&p) {
453                    continue;
454                }
455                let handle = update.get(&p).map(|el| {
456                    el.as_ref()
457                        .map(|el| el.handle)
458                        .unwrap_or(TileDefinitionHandle::EMPTY)
459                });
460                let handle = if let Some(handle) = handle {
461                    handle
462                } else if let Some(tiles) = tiles.as_ref() {
463                    tiles.get(p).unwrap_or(TileDefinitionHandle::EMPTY)
464                } else {
465                    TileDefinitionHandle::EMPTY
466                };
467                let pattern = pattern_property
468                    .get_from_tile_set(tile_set, handle)
469                    .unwrap_or_default();
470                edge_restrictions.insert(p, PatternBits(pattern.into()));
471            }
472        }
473        for (p, pattern) in edge_restrictions.drain() {
474            self.restrict_edge(&p, &pattern, constraint)?;
475        }
476        self.edge_restrictions = edge_restrictions;
477        Ok(())
478    }
479    /// Modify the given tile map update based on the result of the
480    /// autotiler.
481    pub fn apply_autotile_to_update<R: Rng + ?Sized>(
482        &self,
483        rng: &mut R,
484        value_map: &TileSetWfcConstraint,
485        update: &mut MacroTilesUpdate,
486    ) {
487        for (pos, pat) in self.assigned_patterns() {
488            let Some(&handle) = value_map.get_random(rng, pat) else {
489                continue;
490            };
491            let source = update.get(pos).cloned().flatten().and_then(|el| el.source);
492            let handle = if handle.is_empty() {
493                None
494            } else {
495                Some(StampElement { handle, source })
496            };
497            _ = update.insert(*pos, handle);
498        }
499    }
500    /// Modify the given tile map data based on the result of the
501    /// autotiler.
502    pub fn apply_autotile_to_data<R: Rng + ?Sized>(
503        &self,
504        rng: &mut R,
505        value_map: &TileSetWfcConstraint,
506        data: &mut TileMapData,
507    ) {
508        for (pos, pat) in self.assigned_patterns() {
509            let Some(&handle) = value_map.get_random(rng, pat) else {
510                continue;
511            };
512            data.set(*pos, handle);
513        }
514    }
515}