fyrox_autotile/
auto.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, ops::Deref};
22
23use super::*;
24
25/// The contraints that control how the autotiler searches for tiles at each position.
26pub trait AutoConstrain {
27    /// The type of a tile's position.
28    type Position: OffsetPosition;
29    /// The type of tile terrains. Each terrain represents a set of possible patterns.
30    type Terrain: TileTerrain;
31    /// An iterator for all the positions that the autotiler should consider when
32    /// choosing tiles. The autotiler will select tiles for position in the order
33    /// they are delivered by this iterator.
34    fn all_positions(&self) -> impl Iterator<Item = &Self::Position>;
35    /// The contraint for the given position. This allows the autotiler to examine
36    /// adjacent cells when deciding if a particular pattern is a valid choice
37    /// for the cell under consideration.
38    fn constraint_at(
39        &self,
40        position: &Self::Position,
41    ) -> TileConstraint<Self::Terrain, <Self::Terrain as TileTerrain>::Pattern>;
42    /// True if the given patterns may be placed adjacent to each other with the
43    /// given offset. For example, if `offset` is up, then return true if
44    /// `to` may be legally placed above `from`.
45    fn is_legal(
46        &self,
47        from: &<Self::Terrain as TileTerrain>::Pattern,
48        offset: &<Self::Position as OffsetPosition>::Offset,
49        to: &<Self::Terrain as TileTerrain>::Pattern,
50    ) -> bool;
51    /// True if the given patterns may be placed adjacent to each other with the
52    /// given offset. For example, if `offset` is up, then return true if
53    /// `to` may be legally placed above `from`.
54    fn is_legal_diagonal(
55        &self,
56        from: &<Self::Terrain as TileTerrain>::Pattern,
57        diagonal: &<Self::Position as OffsetPosition>::Diagonal,
58        to: &<Self::Terrain as TileTerrain>::Pattern,
59    ) -> bool;
60}
61
62/// For autotiling, tiles are grouped into patterns, and patterns are grouped
63/// into terrains. Each cell that is to be autotiled is given a terrain, and
64/// the autotiler must choose a pattern from within that terrain.
65pub trait TileTerrain {
66    /// The type of the patterns within this terrain.
67    type Pattern;
68    /// Iterate through the patterns within the terrain in the order of priority.
69    /// The autotiler will proceed through this iterator until it finds a pattern
70    /// that is legal according to the constraints, then ignore any remaining patterns.
71    fn all_patterns(&self) -> impl Iterator<Item = &Self::Pattern>;
72}
73
74/// Trait for objects that represent a particular autotiling problem to be solved,
75/// with a set of positions that need tiles and a terrain for each position to control
76/// which tiles are valid.
77pub trait TerrainSource {
78    /// The type of positions that need tiles.
79    type Position;
80    /// The type of the terrains that control which patterns are allowed at each position.
81    type Terrain;
82    /// Iterate over the positions that need tiles and the terrains at each position.
83    fn iter(&self) -> impl Iterator<Item = NeededTerrain<Self::Position, Self::Terrain>> + '_;
84    /// True if the given position needs to be tiled.
85    fn contains_position(&self, position: &Self::Position) -> bool;
86}
87
88/// This represents a cell that needs to be autotiled, including its position,
89/// its terrain, and the rules for what to do with the surrounding tiles.
90pub struct NeededTerrain<Pos, Ter> {
91    /// The position of the cell to be autotiled.
92    pub position: Pos,
93    /// The terrain which specifies the valid patterns for the cell.
94    pub terrain: Ter,
95    /// The rules for whether surrounding tiles may be changed while
96    /// autitiling this cell.
97    pub fill: ConstraintFillRules,
98}
99
100/// Trait for objects that represent a constraining environment for the autotiler.
101/// Given any position, a `PatternSource` object can supply a [`TileConstraint`] for that position.
102/// While a [`TerrainSource`] object alone may be enough to tell the autotiler what positions it needs
103/// to fill with tiles, a `PatternSource` can provide information about the neighboring tiles, and thus
104/// give the autotiler a complete picture of the environment it is tiling.
105pub trait PatternSource {
106    /// The type of positions that need tiles.
107    type Position;
108    /// The type of the terrains that control which patterns are allowed at each position.
109    type Terrain;
110    /// The type of the patterns that the autotiler must choose for each position.
111    type Pattern;
112    /// The contraint for the cell at the given position.
113    fn get(&self, position: &Self::Position) -> TileConstraint<Self::Terrain, Self::Pattern>;
114    /// The terrain of the pattern at the given position.
115    fn get_terrain(&self, position: &Self::Position) -> Option<Self::Terrain>;
116}
117
118/// When a cell is autotiled it may be desired that adjacent cells should be modified
119/// to fit with the new terrain of this cell. This object specifies whether that should
120/// be done. Cells that are added this way keep their current terrain, but their pattern
121/// may change.
122#[derive(Debug, Default, Clone, Copy)]
123pub struct ConstraintFillRules {
124    /// True if adjacent cells should be automatically added to the autotiling constraint
125    /// so that they may be modified.
126    pub include_adjacent: bool,
127    /// True if diagonal cells should be automatically added to the autotiling constraint
128    /// so that they may be modified.
129    pub include_diagonal: bool,
130}
131
132/// A pattern source that is stored in a hash map.
133#[derive(Clone)]
134pub struct HashConstraintMap<Pos, Ter, Pat> {
135    constraints: FxHashMap<Pos, TileConstraint<Ter, Pat>>,
136    terrain_cells: Vec<Pos>,
137}
138
139impl<Ter: Debug, Pat: Debug> Debug for HashConstraintMap<Vector2<i32>, Ter, Pat> {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        f.write_str("HashConstraintMap:\n")?;
142        for (p, v) in self.constraints.iter() {
143            writeln!(f, " ({:2},{:2})->{v:?}", p.x, p.y)?;
144        }
145        write!(f, "terrain_cells: {:?}", self.terrain_cells)
146    }
147}
148
149impl<Pos, Ter, Pat> HashConstraintMap<Pos, Ter, Pat>
150where
151    Pos: Hash + Eq + OffsetPosition,
152    Ter: Clone,
153    Pat: Clone,
154{
155    /// Replace the content of this map using the given `PatternSource` and `TerrainSource`.
156    /// The `TerrainSource` specifies certain cells that need to be given tiles by specifying
157    /// the required terrain for those tiles. The terrains and patterns for the surrounding
158    /// cells are taken from the `PatternSource`.
159    ///
160    /// The immediately adjacent cells are added to the front of the list of cells to be
161    /// assigned tiles and their terrain is taken from the `PatternSource`. The cells that
162    /// are one further away are make immutable by constraining them to be exactly their
163    /// current pattern from `PatternSource`.
164    pub fn fill_from<T, P>(&mut self, terrains: &T, patterns: &P)
165    where
166        T: TerrainSource<Position = Pos, Terrain = Ter>,
167        P: PatternSource<Position = Pos, Terrain = Ter, Pattern = Pat>,
168    {
169        self.clear();
170        for ter in terrains.iter() {
171            if ter.fill.include_diagonal {
172                for offset in Pos::all_diagonals() {
173                    let p = ter.position.clone() + offset;
174                    if !self.terrain_cells.contains(&p) && !terrains.contains_position(&p) {
175                        if let Some(terrain) = patterns.get_terrain(&p) {
176                            self.insert(p.clone(), TileConstraint::Terrain(terrain));
177                        }
178                    }
179                }
180            }
181            if ter.fill.include_adjacent {
182                for offset in Pos::all_offsets() {
183                    let p = ter.position.clone() + offset;
184                    if !self.terrain_cells.contains(&p) && !terrains.contains_position(&p) {
185                        if let Some(terrain) = patterns.get_terrain(&p) {
186                            self.insert(p.clone(), TileConstraint::Terrain(terrain));
187                        }
188                    }
189                }
190            }
191        }
192        for ter in terrains.iter() {
193            self.insert(ter.position, TileConstraint::Terrain(ter.terrain));
194        }
195        for pos in self.terrain_cells.clone().iter() {
196            for offset in Pos::all_offsets() {
197                let p = pos.clone() + offset;
198                if !self.terrain_cells.contains(&p) {
199                    self.insert(p.clone(), patterns.get(&p));
200                }
201            }
202        }
203    }
204}
205
206impl<Pos, Ter, Pat> HashConstraintMap<Pos, Ter, Pat>
207where
208    Pos: Hash + Eq + Clone,
209{
210    /// Makes this map empty, so all cells have [`TileConstraint::None`].
211    pub fn clear(&mut self) {
212        self.constraints.clear();
213        self.terrain_cells.clear();
214    }
215    /// Add the given constraint to this map.
216    pub fn insert(&mut self, pos: Pos, constraint: TileConstraint<Ter, Pat>) {
217        if constraint.is_terrain() {
218            self.terrain_cells.push(pos.clone());
219        }
220        _ = self.constraints.insert(pos, constraint);
221    }
222    /// Get the constraint for the given position.
223    pub fn get(&self, pos: &Pos) -> &TileConstraint<Ter, Pat> {
224        self.constraints.get(pos).unwrap_or_default()
225    }
226    /// Iterate over all the terrain cells that are to be tiled in the order
227    /// that they should be tiled.
228    pub fn all_positions(&self) -> impl Iterator<Item = &Pos> {
229        self.terrain_cells.iter()
230    }
231}
232
233impl<Pos, Ter, Pat> Default for HashConstraintMap<Pos, Ter, Pat> {
234    fn default() -> Self {
235        Self {
236            constraints: FxHashMap::default(),
237            terrain_cells: Vec::default(),
238        }
239    }
240}
241
242impl<Pos, Ter, Pat> Deref for HashConstraintMap<Pos, Ter, Pat> {
243    type Target = FxHashMap<Pos, TileConstraint<Ter, Pat>>;
244
245    fn deref(&self) -> &Self::Target {
246        &self.constraints
247    }
248}
249
250/// The ways in which a cell's choice of tile can be constrained.
251#[derive(Debug, Clone)]
252pub enum TileConstraint<T, P> {
253    /// No constraint. This means that the cell is outside of the area of consideration
254    /// for the autotiler, such as beyond the edge of the world. Cells with this constraint
255    /// put no limits on what may be adjacent in any direction.
256    None,
257    /// A terrain is a set of possible patterns. Any pattern within the terrain may be chosen
258    /// to be the pattern for this cell.
259    Terrain(T),
260    /// A pattern constraint means that the pattern for this cell has already been chosen and
261    /// may not change.
262    Pattern(P),
263}
264
265impl<T, P> Default for TileConstraint<T, P> {
266    fn default() -> Self {
267        Self::None
268    }
269}
270
271impl<T, P> Default for &TileConstraint<T, P> {
272    fn default() -> Self {
273        &TileConstraint::None
274    }
275}
276
277impl<T, P> TileConstraint<T, P> {
278    /// True if this is the None constraint that does not restrict which pattern
279    /// may be chosen.
280    pub fn is_none(&self) -> bool {
281        matches!(self, Self::None)
282    }
283    /// True if this constraint is not None, meaning it is either a pattern or a terrain.
284    pub fn is_some(&self) -> bool {
285        !self.is_none()
286    }
287    /// True if this constraint is a terrain, meaning there are zero or more permitted patterns.
288    pub fn is_terrain(&self) -> bool {
289        matches!(self, Self::Terrain(_))
290    }
291    /// Tue if this contraint is a pattern, meaning that only that exact pattern is permitted.
292    pub fn is_pattern(&self) -> bool {
293        matches!(self, Self::Pattern(_))
294    }
295    /// An iterator over all the patterns explicitly permitted by this contraint,
296    /// or an empty iterator if the constraint is None.
297    /// The None constraint permits any pattern, but it is not possible to create
298    /// an interator over all patterns here, so `all_patterns` should usually only
299    /// be called if [`is_some`](Self::is_some) returns true.
300    pub fn all_patterns(&self) -> TileConstraint<impl Iterator<Item = &P>, &P>
301    where
302        T: TileTerrain<Pattern = P>,
303    {
304        match self {
305            Self::None => TileConstraint::None,
306            Self::Terrain(t) => TileConstraint::Terrain(t.all_patterns()),
307            Self::Pattern(p) => TileConstraint::Pattern(p),
308        }
309    }
310}
311
312impl<T, P> Iterator for TileConstraint<T, P>
313where
314    T: Iterator<Item = P>,
315    P: Clone,
316{
317    type Item = P;
318
319    fn next(&mut self) -> Option<Self::Item> {
320        match self {
321            Self::None => None,
322            Self::Terrain(t) => t.next(),
323            Self::Pattern(p) => {
324                let p = p.clone();
325                *self = Self::None;
326                Some(p)
327            }
328        }
329    }
330}
331
332/// The object responsible for autotiling and owning a hash map
333/// were the result is stored.
334#[derive(Debug, Default, Clone)]
335pub struct AutoTiler<Pos, Pat> {
336    patterns: FxHashMap<Pos, Pat>,
337}
338
339impl<Pos, Pat> std::ops::Deref for AutoTiler<Pos, Pat> {
340    type Target = FxHashMap<Pos, Pat>;
341
342    fn deref(&self) -> &Self::Target {
343        &self.patterns
344    }
345}
346
347impl<Pos, Pat> std::ops::DerefMut for AutoTiler<Pos, Pat> {
348    fn deref_mut(&mut self) -> &mut Self::Target {
349        &mut self.patterns
350    }
351}
352
353impl<Pos: OffsetPosition, Pat: Clone + Debug> AutoTiler<Pos, Pat> {
354    /// Fill the autotile map with patterns according to the given constraint.
355    /// The hash map is not automatically cleared by this method, so it may be
356    /// called multiple times with different constraints to build up a single
357    /// autotile solution.
358    pub fn autotile<C, T>(&mut self, constraint: &C)
359    where
360        C: AutoConstrain<Position = Pos, Terrain = T>,
361        T: TileTerrain<Pattern = Pat> + Debug,
362        Pos: Debug,
363        <Pos as OffsetPosition>::Offset: Debug,
364    {
365        for pos in constraint.all_positions() {
366            if !self.patterns.contains_key(pos) {
367                if let Some(pat) = self.find_pattern(pos, constraint) {
368                    _ = self.patterns.insert(pos.clone(), pat);
369                }
370            }
371        }
372    }
373    fn find_pattern<C, T>(&self, position: &Pos, constraint: &C) -> Option<Pat>
374    where
375        C: AutoConstrain<Position = Pos, Terrain = T>,
376        T: TileTerrain<Pattern = Pat> + Debug,
377        Pos: Debug,
378        <Pos as OffsetPosition>::Offset: Debug,
379    {
380        for pat in constraint.constraint_at(position).all_patterns() {
381            if self.is_pattern_legal(position, pat, constraint) {
382                return Some(pat.clone());
383            }
384        }
385        None
386    }
387    fn is_pattern_legal<C, T>(&self, position: &Pos, pattern: &Pat, constraint: &C) -> bool
388    where
389        C: AutoConstrain<Position = Pos, Terrain = T>,
390        T: TileTerrain<Pattern = Pat>,
391        <Pos as OffsetPosition>::Offset: Debug,
392    {
393        for diagonal in Pos::all_diagonals() {
394            let p = position.clone() + diagonal.clone();
395            if let Some(pat) = self.patterns.get(&p) {
396                if !constraint.is_legal_diagonal(pattern, &diagonal, pat) {
397                    return false;
398                }
399            } else {
400                let cell_constraint = constraint.constraint_at(&p);
401                if cell_constraint.is_some()
402                    && !cell_constraint
403                        .all_patterns()
404                        .any(|pat| constraint.is_legal_diagonal(pattern, &diagonal, pat))
405                {
406                    return false;
407                }
408            }
409        }
410        for offset in Pos::all_offsets() {
411            let p = position.clone() + offset.clone();
412            if let Some(pat) = self.patterns.get(&p) {
413                if !constraint.is_legal(pattern, &offset, pat) {
414                    return false;
415                }
416            } else {
417                let cell_constraint = constraint.constraint_at(&p);
418                if cell_constraint.is_some()
419                    && !cell_constraint
420                        .all_patterns()
421                        .any(|pat| constraint.is_legal(pattern, &offset, pat))
422                {
423                    return false;
424                }
425            }
426        }
427        true
428    }
429}
430
431/// This contains the background information required for autotiling
432/// independent of the particular cells to be filled, so it can be used
433/// for multiple autotiling tasks. It contains a hash map from terrain
434/// values to lists of patterns, representing both the patterns contained
435/// within each terrain and the order in which the patterns should be examined.
436/// It also contains and a hash map from patterns to a [`ProbabilitySet`] of tiles,
437/// representing how a tile should be randomly selected once a pattern is chosen.
438#[derive(Clone)]
439pub struct AutoTileContext<Ter, Pat, Tile> {
440    pattern_list_pool: Vec<Vec<Pat>>,
441    probability_set_pool: Vec<ProbabilitySet<Tile>>,
442    /// Hash map from terrain values to the list of pattern values that each terrain represents.
443    /// The pattern values should be sorted into the order in which the autotiler will try to insert
444    /// the patterns into the cells.
445    pub patterns: AutoTerrainPatternMap<Ter, Pat>,
446    /// Hash map from pattern values to [`ProbabilitySet`] of tile values, giving each tile the probability
447    /// it should have when randomly selecting a tile for a given pattern.
448    pub values: AutoPatternValueMap<Pat, Tile>,
449}
450
451impl<Ter: Debug, Pat: Debug, Tile: Debug> Debug for AutoTileContext<Ter, Pat, Tile> {
452    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
453        f.write_str("AutoTileContext\n")?;
454        f.write_str("patterns:\n")?;
455        for (p, v) in self.patterns.iter() {
456            writeln!(f, "{p:?} -> {v:?}")?;
457        }
458        f.write_str("values:\n")?;
459        for (p, v) in self.values.iter() {
460            writeln!(f, "{p:?} -> {v:?}")?;
461        }
462        Ok(())
463    }
464}
465
466impl<Ter, Pat, Tile> Default for AutoTileContext<Ter, Pat, Tile> {
467    fn default() -> Self {
468        Self {
469            pattern_list_pool: Default::default(),
470            probability_set_pool: Default::default(),
471            patterns: Default::default(),
472            values: Default::default(),
473        }
474    }
475}
476
477impl<Ter: Eq + Hash, Pat: Eq + Hash + Clone, Tile> AutoTileContext<Ter, Pat, Tile> {
478    /// True if this context contains no patterns.
479    pub fn is_empty(&self) -> bool {
480        self.patterns.is_empty()
481    }
482    /// Make the context empty in preparation for building a new context.
483    pub fn clear(&mut self) {
484        for (_, mut list) in self.patterns.drain() {
485            list.clear();
486            self.pattern_list_pool.push(list);
487        }
488        for (_, mut set) in self.values.drain() {
489            set.clear();
490            self.probability_set_pool.push(set);
491        }
492    }
493    /// Add a particular tile to the context and specify that tile's terrain, pattern,
494    /// and frequency. The higher the frequency, the more likely this tile will be chosen
495    /// if it shares the same pattern as another tile.
496    pub fn add(&mut self, key: Ter, pattern: Pat, frequency: f32, value: Tile) {
497        self.patterns
498            .entry(key)
499            .or_insert_with(|| self.pattern_list_pool.pop().unwrap_or_default())
500            .push(pattern.clone());
501        self.values
502            .entry(pattern)
503            .or_insert_with(|| self.probability_set_pool.pop().unwrap_or_default())
504            .add(frequency, value);
505    }
506    /// Sort the pattern values according to their natural order.
507    pub fn sort(&mut self)
508    where
509        Pat: Ord,
510    {
511        for pattern_list in self.patterns.values_mut() {
512            pattern_list.sort();
513            pattern_list.dedup();
514        }
515    }
516    /// Get a random tile that has the given pattern, if possible.
517    pub fn get_random_value<R: Rng + ?Sized>(&self, rng: &mut R, pattern: &Pat) -> Option<&Tile> {
518        self.values.get(pattern).and_then(|vs| vs.get_random(rng))
519    }
520}
521
522/// A hash map from terrain values to lists of patterns.
523/// Each pattern has a terrain, and this map is used to list all of
524/// the patterns for a given terrain id value in the order of priority.
525/// If multiple patterns in the list may be chosen for some cell,
526/// the pattern nearest the front of the list should be chosen.
527///
528/// * Ter: The type of the terrain ids that are to be associated with lists of patterns.
529/// * Pat: The type of a pattern.
530///
531/// See [`AutoTileContext`] for a way to construct an `AutoTerrainPatternMap`.
532/// Once an `AutoTerrainPatternMap` has been constructed, it may be
533/// used as part of an [`AutoPatternConstraint`].
534pub type AutoTerrainPatternMap<Ter, Pat> = FxHashMap<Ter, Vec<Pat>>;
535
536/// A hash map from patterns to tiles. It is possible for multiple tiles to share
537/// the same pattern, and in that case there is no way for the autotiler to deterministically
538/// choose a tile. This map gives each pattern a [`ProbabilitySet`] that can be used
539/// to randomly select a tile based upon the relative frequency of each tile.
540///
541/// * Pat: The type of a tile pattern.
542/// * Tile: The type of a tile.
543///
544/// See [`AutoTileContext`] for a way to construct an `AutoPatternValueMap`.
545pub type AutoPatternValueMap<Pat, Tile> = FxHashMap<Pat, ProbabilitySet<Tile>>;
546
547/// A pair of an [`HashConstraintMap`] and an [`AutoTerrainPatternMap`].
548/// - The constraint map tells the auto-tiler which cells have undecided patterns,
549///   which cells have fixed patterns, the terrain type of the undecided cells, and
550///   the order in which to fill them.
551/// - The terrain map tells the auto-tiler which patterns are available for each
552///   terrain type, and the order in which the patterns should be tried.
553///
554/// See [`AutoTileContext`] for a way to construct an [`AutoTerrainPatternMap`].
555pub struct AutoPatternConstraint<'a, 'b, Pos, Ter, Pat> {
556    /// The position constraints define the specific problem for the autotiler to solve
557    /// by giving it the constraints for each cell, including the cells whose tiles are already
558    /// determined and the cells that are yet to be determined.
559    pub position_constraints: &'a HashConstraintMap<Pos, Ter, Pat>,
560    /// The pattern contraints define the patterns that are available for each terrain.
561    /// This will usually remain fixed across many calls to the autotiler, and so it may be reused
562    /// so long as the set of tiles does not change.
563    /// See [`AutoTileContext`] for a way to construct an [`AutoTerrainPatternMap`].
564    pub pattern_constraints: &'b AutoTerrainPatternMap<Ter, Pat>,
565}
566
567impl<'b, Pos, Ter, Pat> AutoConstrain for AutoPatternConstraint<'_, 'b, Pos, Ter, Pat>
568where
569    Pos: OffsetPosition,
570    Ter: Hash + Eq,
571    Pat: TilePattern<Offset = Pos::Offset, Diagonal = Pos::Diagonal> + Clone,
572    Pat: Debug,
573    Pos::Offset: Debug,
574{
575    type Position = Pos;
576
577    type Terrain = ListTerrain<'b, Pat>;
578
579    fn all_positions(&self) -> impl Iterator<Item = &Self::Position> {
580        self.position_constraints.all_positions()
581    }
582
583    fn constraint_at(&self, position: &Pos) -> TileConstraint<ListTerrain<'b, Pat>, Pat> {
584        match self.position_constraints.get(position) {
585            TileConstraint::Terrain(ter) => match self.pattern_constraints.get(ter) {
586                Some(pat_list) => TileConstraint::Terrain(ListTerrain(pat_list)),
587                None => TileConstraint::None,
588            },
589            TileConstraint::Pattern(pat) => TileConstraint::Pattern(pat.clone()),
590            TileConstraint::None => TileConstraint::None,
591        }
592    }
593
594    fn is_legal(&self, from: &Pat, offset: &Pos::Offset, to: &Pat) -> bool {
595        from.is_legal(offset, to)
596    }
597    fn is_legal_diagonal(&self, from: &Pat, diagonal: &Pos::Diagonal, to: &Pat) -> bool {
598        from.is_legal_diagonal(diagonal, to)
599    }
600}
601
602/// An implementation of [`TileTerrain`] based upon a slice of patterns.
603#[derive(Debug, Clone)]
604pub struct ListTerrain<'a, P>(pub &'a [P]);
605
606impl<P> TileTerrain for ListTerrain<'_, P> {
607    type Pattern = P;
608
609    fn all_patterns(&self) -> impl Iterator<Item = &P> {
610        self.0.iter()
611    }
612}