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(¢er) {
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}