Skip to main content

fyrox_autotile/
lib.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//! Autotiling allows you to fill the content of a grid according to pre-defined rules.
22//! Tiles are assigned frequencies and adjacency rules, and then an algorithm is used
23//! to attempt to fill a given area with tiles in accordance with the rules and frequencies.
24//!
25//! This library supports both deterministic autotiling and wave function collapse.
26//! Deterministic autotiling tries to find the particular tile that the user wants
27//! based upon giving specification for the purpose of streamlining creating tile-based art.
28//! Wave function collapse is a process of automatically generating random content where
29//! the algorithm is giving freedom to choose whatever tiles it likes within the limits
30//! of the adjacency rules.
31//!
32//! The autotiling algorithm is based upon [Terrain Autotiler](https://github.com/dandeliondino/terrain-autotiler).
33//!
34//! The wave function collapse algorith is based upon [fast-wfc](https://github.com/math-fehr/fast-wfc).
35#![warn(missing_docs)]
36
37use std::{collections::hash_map::Entry, fmt::Debug, hash::Hash};
38
39use fxhash::FxHashMap;
40use nalgebra::{Vector2, Vector3};
41use rand::Rng;
42
43mod auto;
44mod wave;
45
46pub use auto::*;
47pub use wave::*;
48
49/// Position types for tiles that provides abstract access to all of the position's
50/// adjacent positions.
51pub trait OffsetPosition:
52    Eq
53    + Hash
54    + Clone
55    + std::ops::Add<Self::Offset, Output = Self>
56    + std::ops::Add<Self::Diagonal, Output = Self>
57{
58    /// An offset from a position to one of its adjacent positions.
59    /// Since `OffsetPosition` implements `Add<Offset, Ouput=Self>`, an offset can be added to a position
60    /// to get the adjacent position.
61    type Offset: std::ops::Neg<Output = Self::Offset> + Clone;
62    /// A diagonal offset from a position to one of the positions that are nearby but not adjacent.
63    /// Since `OffsetPosition` implements `Add<Diagonal, Ouput=Self>`, a diagonal can be added to a position
64    /// to get the diagonal position.
65    type Diagonal: std::ops::Neg<Output = Self::Diagonal> + Clone;
66    /// An iterator over all offsets, giving access to all adjacent positions from any position by using
67    /// position arithmetic: position + offset == adjacent position.
68    fn all_offsets() -> impl Iterator<Item = Self::Offset>;
69    /// An iterator over all diagonals, giving access to nearby non-adjacent positions by using position
70    /// arithmetic: position + diagonal == nearby position.
71    fn all_diagonals() -> impl Iterator<Item = Self::Diagonal>;
72}
73
74impl OffsetPosition for Vector2<i32> {
75    type Offset = Vector2Offset;
76    type Diagonal = Vector2Diagonal;
77    fn all_offsets() -> impl Iterator<Item = Self::Offset> {
78        (0..4).map(Vector2Offset)
79    }
80    fn all_diagonals() -> impl Iterator<Item = Self::Diagonal> {
81        (0..4).map(Vector2Diagonal)
82    }
83}
84
85/// An offset to move from a `Vector2<i32>` position to an diagonal `Vector2<i32>`.
86#[derive(Clone, Copy, PartialEq, Eq)]
87pub struct Vector2Diagonal(usize);
88
89impl Vector2Diagonal {
90    /// Diagonal (-1, -1)
91    pub const LEFT_DOWN: Vector2Diagonal = Vector2Diagonal(0);
92    /// Diagonal (1, -1)
93    pub const RIGHT_DOWN: Vector2Diagonal = Vector2Diagonal(1);
94    /// Diagonal (-1, 1)
95    pub const LEFT_UP: Vector2Diagonal = Vector2Diagonal(2);
96    /// Diagonal (1, 1)
97    pub const RIGHT_UP: Vector2Diagonal = Vector2Diagonal(3);
98    /// The bit in a `PatternBits` object that corresponds to this diagonal.
99    /// The returned bit is the peer that must match against the diagonal pattern
100    /// in this direction.
101    fn peering_bit(&self) -> usize {
102        DIAGONAL_PEERING_BITS[self.0]
103    }
104    /// The x difference of the offset, so position.x + dx == adjacent.x
105    pub fn dx(&self) -> i32 {
106        DIAG2[self.0].x
107    }
108    /// The y difference of the offset, so position.y + dy == adjacent.y
109    pub fn dy(&self) -> i32 {
110        DIAG2[self.0].y
111    }
112}
113
114impl Debug for Vector2Diagonal {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        f.debug_struct("Vector2Diagonal")
117            .field("dx", &self.dx())
118            .field("dy", &self.dy())
119            .finish()
120    }
121}
122
123impl std::ops::Add<Vector2Diagonal> for Vector2<i32> {
124    type Output = Vector2<i32>;
125
126    fn add(self, rhs: Vector2Diagonal) -> Self::Output {
127        self + DIAG2[rhs.0]
128    }
129}
130
131impl std::ops::Neg for Vector2Diagonal {
132    type Output = Self;
133
134    fn neg(self) -> Self::Output {
135        Vector2Diagonal(3 - self.0)
136    }
137}
138
139/// An offset to move from a `Vector2<i32>` position to an adjacent `Vector2<i32>`
140/// in one of the four cardinal directions.
141#[derive(Clone, Copy, PartialEq, Eq)]
142pub struct Vector2Offset(usize);
143
144const OFFSETS2: [Vector2<i32>; 4] = [
145    Vector2::new(-1, 0),
146    Vector2::new(0, -1),
147    Vector2::new(0, 1),
148    Vector2::new(1, 0),
149];
150
151const DIAG2: [Vector2<i32>; 4] = [
152    Vector2::new(-1, -1),
153    Vector2::new(1, -1),
154    Vector2::new(-1, 1),
155    Vector2::new(1, 1),
156];
157
158const fn bit_pos(x: usize, y: usize) -> usize {
159    x + y * 3
160}
161
162const OFFSET_PEERING_BITS: [[usize; 3]; 4] = [
163    [bit_pos(0, 0), bit_pos(0, 1), bit_pos(0, 2)],
164    [bit_pos(0, 0), bit_pos(1, 0), bit_pos(2, 0)],
165    [bit_pos(0, 2), bit_pos(1, 2), bit_pos(2, 2)],
166    [bit_pos(2, 0), bit_pos(2, 1), bit_pos(2, 2)],
167];
168
169const DIAGONAL_PEERING_BITS: [usize; 4] =
170    [bit_pos(0, 0), bit_pos(2, 0), bit_pos(0, 2), bit_pos(2, 2)];
171
172impl Vector2Offset {
173    /// Offset (0, 1)
174    pub const UP: Vector2Offset = Vector2Offset(2);
175    /// Offset (0, -1)
176    pub const DOWN: Vector2Offset = Vector2Offset(1);
177    /// Offset (-1, 0)
178    pub const LEFT: Vector2Offset = Vector2Offset(0);
179    /// Offset (1, 0)
180    pub const RIGHT: Vector2Offset = Vector2Offset(3);
181    /// Iterator over the three peering bits that are along the edge
182    /// of a 3x3 pattern grid in the direction of this offset.
183    fn peering_bits(&self) -> impl Iterator<Item = usize> {
184        OFFSET_PEERING_BITS[self.0].iter().copied()
185    }
186    /// The x difference of the offset, so position.x + dx == adjacent.x
187    pub fn dx(&self) -> i32 {
188        OFFSETS2[self.0].x
189    }
190    /// The y difference of the offset, so position.y + dy == adjacent.y
191    pub fn dy(&self) -> i32 {
192        OFFSETS2[self.0].y
193    }
194}
195
196impl Debug for Vector2Offset {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        f.debug_struct("Vector2Offset")
199            .field("dx", &self.dx())
200            .field("dy", &self.dy())
201            .finish()
202    }
203}
204
205impl From<Vector2Offset> for Vector2<i32> {
206    fn from(value: Vector2Offset) -> Self {
207        OFFSETS2[value.0]
208    }
209}
210
211impl std::ops::Add<Vector2Offset> for Vector2<i32> {
212    type Output = Vector2<i32>;
213
214    fn add(self, rhs: Vector2Offset) -> Self::Output {
215        self + OFFSETS2[rhs.0]
216    }
217}
218
219impl std::ops::Neg for Vector2Offset {
220    type Output = Self;
221
222    fn neg(self) -> Self::Output {
223        Vector2Offset(3 - self.0)
224    }
225}
226
227impl OffsetPosition for Vector3<i32> {
228    type Offset = Vector3Offset;
229    type Diagonal = Vector3Diagonal;
230    fn all_offsets() -> impl Iterator<Item = Self::Offset> {
231        (0..6).map(Vector3Offset)
232    }
233    fn all_diagonals() -> impl Iterator<Item = Self::Diagonal> {
234        (0..DIAG3.len()).map(|i| DIAG3[i]).map(Vector3Diagonal)
235    }
236}
237
238/// An offset to move from a `Vector2<i32>` position to an diagonal `Vector2<i32>`.
239#[derive(Clone, Copy, PartialEq, Eq)]
240pub struct Vector3Diagonal(Vector3<i32>);
241
242const DIAG3: [Vector3<i32>; 20] = [
243    Vector3::new(-1, -1, -1),
244    Vector3::new(1, -1, -1),
245    Vector3::new(-1, 1, -1),
246    Vector3::new(1, 1, -1),
247    Vector3::new(-1, -1, 0),
248    Vector3::new(1, -1, 0),
249    Vector3::new(-1, 1, 0),
250    Vector3::new(1, 1, 0),
251    Vector3::new(-1, -1, 1),
252    Vector3::new(1, -1, 1),
253    Vector3::new(-1, 1, 1),
254    Vector3::new(1, 1, 1),
255    Vector3::new(-1, 0, -1),
256    Vector3::new(0, -1, -1),
257    Vector3::new(1, 0, -1),
258    Vector3::new(0, 1, -1),
259    Vector3::new(-1, 0, 1),
260    Vector3::new(0, -1, 1),
261    Vector3::new(1, 0, 1),
262    Vector3::new(0, 1, 1),
263];
264
265impl std::ops::Add<Vector3Diagonal> for Vector3<i32> {
266    type Output = Vector3<i32>;
267
268    fn add(self, rhs: Vector3Diagonal) -> Self::Output {
269        self + rhs.0
270    }
271}
272
273impl std::ops::Neg for Vector3Diagonal {
274    type Output = Self;
275
276    fn neg(self) -> Self::Output {
277        Vector3Diagonal(-self.0)
278    }
279}
280
281/// An offset to move from a `Vector3<i32>` position to an adjacent `Vector3<i32>`
282/// in one of the six cardinal directions.
283#[derive(Clone, Copy, PartialEq, Eq)]
284pub struct Vector3Offset(usize);
285
286impl Vector3Offset {
287    /// The x difference of the offset, so position.x + dx == adjacent.x
288    pub fn dx(&self) -> i32 {
289        OFFSETS3[self.0].x
290    }
291    /// The y difference of the offset, so position.y + dy == adjacent.y
292    pub fn dy(&self) -> i32 {
293        OFFSETS3[self.0].y
294    }
295    /// The z difference of the offset, so position.z + dz == adjacent.z
296    pub fn dz(&self) -> i32 {
297        OFFSETS3[self.0].z
298    }
299}
300
301const OFFSETS3: [Vector3<i32>; 6] = [
302    Vector3::new(-1, 0, 0),
303    Vector3::new(0, -1, 0),
304    Vector3::new(0, 0, -1),
305    Vector3::new(0, 0, 1),
306    Vector3::new(0, 1, 0),
307    Vector3::new(1, 0, 0),
308];
309
310impl From<Vector3Offset> for Vector3<i32> {
311    fn from(value: Vector3Offset) -> Self {
312        OFFSETS3[value.0]
313    }
314}
315
316impl std::ops::Add<Vector3Offset> for Vector3<i32> {
317    type Output = Vector3<i32>;
318
319    fn add(self, rhs: Vector3Offset) -> Self::Output {
320        self + OFFSETS3[rhs.0]
321    }
322}
323
324impl std::ops::Neg for Vector3Offset {
325    type Output = Self;
326
327    fn neg(self) -> Self::Output {
328        Vector3Offset(5 - self.0)
329    }
330}
331
332/// A set of values of type `V` where each value is associated with a frequency
333/// so that a value can be chosen from the set at random with the probability of each
334/// value weighted by its frequency.
335#[derive(Debug, Clone)]
336pub struct ProbabilitySet<V> {
337    total: f32,
338    content: Vec<(f32, V)>,
339}
340
341impl<V> Default for ProbabilitySet<V> {
342    fn default() -> Self {
343        Self {
344            total: 0.0,
345            content: Vec::default(),
346        }
347    }
348}
349
350impl<V> ProbabilitySet<V> {
351    /// Iterate through all the items in the set and their frequencies.
352    pub fn iter(&self) -> impl Iterator<Item = (f32, &V)> {
353        self.content.iter().map(|(f, v)| (*f, v))
354    }
355    /// The number of elements in the set.
356    pub fn len(&self) -> usize {
357        self.content.len()
358    }
359    /// True if the set has no elements.
360    pub fn is_empty(&self) -> bool {
361        self.content.is_empty()
362    }
363    /// Remove the content of the set.
364    pub fn clear(&mut self) {
365        self.total = 0.0;
366        self.content.clear();
367    }
368    /// Add a value and give it a frequency.
369    pub fn add(&mut self, frequency: f32, value: V) {
370        if frequency > 0.0 {
371            self.total += frequency;
372            self.content.push((frequency, value));
373        }
374    }
375
376    /// The sum of the frequencies of all the elements of the set.
377    /// The probability of an element being chosen is the frequency of
378    /// that element divided by this total.
379    pub fn total_frequency(&self) -> f32 {
380        self.total
381    }
382
383    /// The mean of the frequencies of all the elements of the set.
384    pub fn average_frequency(&self) -> f32 {
385        let size = self.content.len();
386        if size == 0 {
387            return 0.0;
388        }
389        self.total / (size as f32)
390    }
391
392    /// Choose a value from the set using the given random number generator.
393    /// The probability of each element of the set being chosen is the frequency
394    /// of that element divided by the sum of the frequencies of all elements.
395    pub fn get_random<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<&V> {
396        if self.total <= 0.0 {
397            return None;
398        }
399        let mut p = rng.gen_range(0.0..self.total);
400        for (f, v) in self.iter() {
401            if p < f {
402                return Some(v);
403            }
404            p -= f;
405        }
406        self.iter().next().map(|v| v.1)
407    }
408}
409
410/// For the purposes of autotiling, tiles are represented by patterns that
411/// hold the data which determines whether two tiles are permitted to appear
412/// adjacent to each other. Therefore autotile algorithms place patterns
413/// rather than tiles, and translating those patterns into actual tiles
414/// is a later step.
415pub trait TilePattern {
416    /// A pattern is capable of being adjacent to other patterns in several directions.
417    /// The pattern's offset type is the type of the offset between a pattern's position
418    /// and the position of an adjacent pattern.
419    type Offset;
420    /// A pattern may connect to another pattern by touching their corners.
421    /// This creates a diagonal connection where the patterns are touching
422    /// but not adjacent, and in some contexts there may be rules regarding
423    /// which patterns may be diagonally connected to which other patterns.
424    type Diagonal;
425    /// True if this pattern may be adjacent the given other pattern of this type
426    /// when that other pattern is positioned at `offset` relative to this pattern.
427    fn is_legal(&self, offset: &Self::Offset, to: &Self) -> bool;
428    /// True if this pattern may be diagonal to the given other pattern when
429    /// that other pattern is positioned at `offset` relative to this pattern.
430    fn is_legal_diagonal(&self, offset: &Self::Diagonal, to: &Self) -> bool;
431}
432
433/// `PatternBits` represents a tile's pattern as a 3x3 grid of `i8` values
434/// for doing autotiling in 2D with `Vector2<i32>` positions.
435/// The center of the 3x3 grid is called the pattern's *terrain.*
436/// The 8 values around the outside of the grid are called the pattern's
437/// *peering bits.*
438///
439/// The peering bits of this pattern determine whether it is permitted to use this
440/// pattern adjacent to other patterns. A pattern is only legal if all three peering
441/// bits along each edge match the bits on the nearest edge of the adjacent pattern.
442///
443/// In the case of diagonal connections, only only bit must match in each pattern:
444/// the corner bit that is nearest to the other pattern.
445///
446/// For the details of how peering bits constrain where this pattern may be used,
447/// see the implementation of [`TilePattern`].
448#[derive(Default, Clone, Copy, PartialEq, Eq, Hash)]
449pub struct PatternBits(pub [i8; 9]);
450
451impl PatternBits {
452    /// The pattern's terrain ID that can be used to categorize this pattern,
453    /// and to determine which peering bits are the most important for pattern sorting.
454    /// Patterns that have more peering bits that match its terrain are given higher priority
455    /// when sorting and appear earlier in the list.
456    pub fn center(&self) -> i8 {
457        self.0[bit_pos(1, 1)]
458    }
459    /// The number of bits in the pattern that match the terrain ID. This count is at most 9
460    /// and at least 1 because the center bit always matches itself.
461    pub fn center_terrain_count(&self) -> usize {
462        let center = self.center();
463        self.0.iter().filter(|t| **t == center).count()
464    }
465    /// The number of distinct distinct values in this pattern. This count is at most 9 and
466    /// at least 1, for the case where every bit is the same.
467    pub fn unique_terrain_count(&self) -> usize {
468        let mut count = 1;
469        'outer: for i in 1..9 {
470            let current = self.0[i];
471            for j in 0..i {
472                if self.0[j] == current {
473                    continue 'outer;
474                }
475            }
476            count += 1;
477        }
478        count
479    }
480}
481
482impl Debug for PatternBits {
483    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
484        fn w(f: &mut std::fmt::Formatter<'_>, bs: &[i8]) -> std::fmt::Result {
485            write!(f, "{0},{1},{2}", bs[0], bs[1], bs[2])
486        }
487        let bs = &self.0;
488        f.write_str("[")?;
489        w(f, &bs[0..3])?;
490        f.write_str("|")?;
491        w(f, &bs[3..6])?;
492        f.write_str("|")?;
493        w(f, &bs[6..9])?;
494        f.write_str("]")
495    }
496}
497
498impl PartialOrd for PatternBits {
499    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
500        Some(self.cmp(other))
501    }
502}
503
504impl Ord for PatternBits {
505    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
506        self.center_terrain_count()
507            .cmp(&other.center_terrain_count())
508            .reverse()
509            .then_with(|| {
510                self.unique_terrain_count()
511                    .cmp(&other.unique_terrain_count())
512            })
513            .then_with(|| self.0.cmp(&other.0))
514    }
515}
516
517/// Converts an x,y position into index in 0..9. Both x and y must be within 0..3.
518#[inline]
519fn nine_position_to_index(position: Vector2<usize>) -> usize {
520    if position.y > 2 || position.x > 2 {
521        panic!("Illegal terrain bit position: {position:?}");
522    }
523    bit_pos(position.x, position.y)
524}
525
526impl std::ops::Index<Vector2<usize>> for PatternBits {
527    type Output = i8;
528
529    fn index(&self, index: Vector2<usize>) -> &Self::Output {
530        &self.0[nine_position_to_index(index)]
531    }
532}
533
534impl std::ops::IndexMut<Vector2<usize>> for PatternBits {
535    fn index_mut(&mut self, index: Vector2<usize>) -> &mut Self::Output {
536        &mut self.0[nine_position_to_index(index)]
537    }
538}
539
540impl TilePattern for PatternBits {
541    type Offset = Vector2Offset;
542    type Diagonal = Vector2Diagonal;
543    fn is_legal(&self, offset: &Vector2Offset, to: &Self) -> bool {
544        offset
545            .peering_bits()
546            .zip((-*offset).peering_bits())
547            .all(|(a, b)| self.0[a] == to.0[b])
548    }
549    fn is_legal_diagonal(&self, diagonal: &Vector2Diagonal, to: &Self) -> bool {
550        self.0[diagonal.peering_bit()] == to.0[(-*diagonal).peering_bit()]
551    }
552}
553
554#[cfg(test)]
555mod tests {
556    use std::cmp::Ordering;
557
558    use rand::SeedableRng;
559
560    use PatternBits as Bits;
561
562    use super::*;
563
564    fn make_rng(seed: u64) -> rand::rngs::StdRng {
565        rand::rngs::StdRng::seed_from_u64(seed)
566    }
567
568    #[test]
569    fn probability_set_0() {
570        let set = ProbabilitySet::<u32>::default();
571        assert_eq!(set.get_random(&mut make_rng(0)), None);
572    }
573    #[test]
574    fn probability_set_1() {
575        let mut set = ProbabilitySet::<u32>::default();
576        set.add(0.65, 27);
577        let mut rng = make_rng(0);
578        assert_eq!(set.get_random(&mut rng), Some(&27));
579        assert_eq!(set.get_random(&mut rng), Some(&27));
580        assert_eq!(set.get_random(&mut rng), Some(&27));
581    }
582    #[test]
583    fn probability_set_2() {
584        let mut set = ProbabilitySet::<u32>::default();
585        set.add(0.5, 1);
586        set.add(1.0, 2);
587        let mut rng = make_rng(0);
588        let mut results = FxHashMap::<Option<u32>, usize>::default();
589        for _ in 0..75 {
590            *results
591                .entry(set.get_random(&mut rng).cloned())
592                .or_default() += 1;
593        }
594        let result_1 = results.get(&Some(1)).copied().unwrap_or_default();
595        let result_2 = results.get(&Some(2)).copied().unwrap_or_default();
596        assert_eq!(results.get(&None).copied().unwrap_or_default(), 0, "None");
597        assert!((21..27).contains(&result_1), "1: {result_1}");
598        assert!((45..55).contains(&result_2), "2: {result_2}");
599        assert_eq!(results.values().sum::<usize>(), 75, "Sum");
600    }
601    #[test]
602    fn probability_set_3() {
603        let mut set = ProbabilitySet::<u32>::default();
604        set.add(2.0, 1);
605        set.add(2.0, 2);
606        set.add(2.0, 3);
607        let mut rng = make_rng(0);
608        let mut results = FxHashMap::<Option<u32>, usize>::default();
609        for _ in 0..750 {
610            *results
611                .entry(set.get_random(&mut rng).cloned())
612                .or_default() += 1;
613        }
614        let result_1 = results.get(&Some(1)).copied().unwrap_or_default();
615        let result_2 = results.get(&Some(2)).copied().unwrap_or_default();
616        let result_3 = results.get(&Some(2)).copied().unwrap_or_default();
617        assert_eq!(results.get(&None).copied().unwrap_or_default(), 0, "None");
618        assert!((210..270).contains(&result_1), "1: {result_1}");
619        assert!((210..270).contains(&result_2), "2: {result_2}");
620        assert!((210..270).contains(&result_3), "3: {result_3}");
621        assert_eq!(results.values().sum::<usize>(), 750, "Sum");
622    }
623    #[test]
624    fn terrain_ord_1() {
625        let a = Bits([0, 0, 0, 0, 0, 0, 0, 0, 0]);
626        let b = Bits([1, 0, 0, 0, 0, 0, 0, 0, 0]);
627        assert_eq!(a.cmp(&b), Ordering::Less);
628    }
629    #[test]
630    fn terrain_ord_2() {
631        let a = Bits([0, 1, 0, 0, 0, 0, 0, 0, 0]);
632        let b = Bits([0, 1, 0, 0, 0, 0, 0, 0, 0]);
633        assert_eq!(a.cmp(&b), Ordering::Equal);
634    }
635    #[test]
636    fn terrain_ord_3() {
637        let a = Bits([0, 1, 0, 0, 0, 0, 0, 0, 0]);
638        let b = Bits([1, 0, 1, 1, 0, 0, 0, 0, 0]);
639        assert_eq!(a.cmp(&b), Ordering::Less);
640    }
641    #[test]
642    fn terrain_ord_4() {
643        let a = Bits([0, 1, 0, 1, 0, 1, 2, 0, 0]);
644        let b = Bits([1, 0, 1, 1, 0, 0, 0, 0, 0]);
645        assert_eq!(a.cmp(&b), Ordering::Greater);
646    }
647    #[test]
648    fn terrain_ord_ne() {
649        let a = Bits([0, 1, 0, 1, 0, 1, 0, 0, 0]);
650        let b = Bits([1, 0, 1, 1, 0, 0, 0, 0, 0]);
651        assert_ne!(a.cmp(&b), Ordering::Equal);
652    }
653    #[test]
654    fn tile_pattern_up() {
655        let a = Bits([-1, -2, -3, -4, -5, -6, 1, 2, 3]);
656        let b = Bits([1, 2, 3, 4, 5, 6, 7, 8, 9]);
657        let offset = Vector2Offset::DOWN;
658        assert!(b.is_legal(&offset, &a), "{b:?} {offset:?} {a:?}");
659    }
660    #[test]
661    fn tile_pattern_down() {
662        let a = Bits([-1, -2, -3, -4, -5, -6, 1, 2, 3]);
663        let b = Bits([1, 2, 3, 4, 5, 6, 7, 8, 9]);
664        let offset = Vector2Offset::UP;
665        assert!(a.is_legal(&offset, &b), "{b:?} {offset:?} {a:?}");
666    }
667    #[test]
668    fn tile_pattern_right() {
669        let a = Bits([1, 2, 1, 4, 5, 2, 7, 8, 3]);
670        let b = Bits([1, -2, -3, 2, -5, -6, 3, -2, -3]);
671        let offset = Vector2Offset::RIGHT;
672        assert!(a.is_legal(&offset, &b), "{a:?} {offset:?} {b:?}");
673    }
674    #[test]
675    fn tile_pattern_right2() {
676        let a = Bits([3, 3, 0, 3, 3, 0, 3, 3, 0]);
677        let b = Bits([0, 0, 0, 0, 0, 0, 0, 0, 0]);
678        let offset = Vector2Offset::RIGHT;
679        assert!(a.is_legal(&offset, &b), "{a:?} {offset:?} {b:?}");
680    }
681    #[test]
682    fn tile_pattern_left() {
683        let a = Bits([1, 2, 3, 2, 3, 4, 3, 8, 5]);
684        let b = Bits([-1, -2, 1, -2, -5, 2, -3, -2, 3]);
685        let offset = Vector2Offset::LEFT;
686        assert!(a.is_legal(&offset, &b), "{a:?} {offset:?} {b:?}");
687    }
688    #[test]
689    fn tile_pattern_left2() {
690        let a = Bits([0, 3, 3, 0, 3, 3, 0, 3, 3]);
691        let b = Bits([0, 0, 0, 0, 0, 0, 0, 0, 0]);
692        let offset = Vector2Offset::LEFT;
693        assert!(a.is_legal(&offset, &b), "{a:?} {offset:?} {b:?}");
694    }
695    #[test]
696    fn tile_pattern_left_up() {
697        let a = Bits([1, 2, 1, 2, 1, 4, 3, 8, 5]);
698        let b = Bits([-1, -2, 3, -2, -5, -2, -3, -2, -1]);
699        let offset = Vector2Diagonal::LEFT_UP;
700        assert!(a.is_legal_diagonal(&offset, &b), "{a:?} {offset:?} {b:?}");
701    }
702    #[test]
703    fn tile_pattern_right_up() {
704        let a = Bits([1, 2, 1, 2, 1, 4, 1, 8, 3]);
705        let b = Bits([3, -2, -1, -2, -5, -2, -3, -2, -1]);
706        let offset = Vector2Diagonal::RIGHT_UP;
707        assert!(a.is_legal_diagonal(&offset, &b), "{a:?} {offset:?} {b:?}");
708    }
709    #[test]
710    fn tile_pattern_left_down() {
711        let a = Bits([3, 2, 1, 2, 1, 4, 1, 8, 5]);
712        let b = Bits([-1, -2, -3, -2, -5, -2, -3, -2, 3]);
713        let offset = Vector2Diagonal::LEFT_DOWN;
714        assert!(a.is_legal_diagonal(&offset, &b), "{a:?} {offset:?} {b:?}");
715    }
716    #[test]
717    fn tile_pattern_right_down() {
718        let a = Bits([1, 2, 3, 2, 1, 4, 1, 8, 1]);
719        let b = Bits([-3, -2, -1, -2, -5, -2, 3, -2, -1]);
720        let offset = Vector2Diagonal::RIGHT_DOWN;
721        assert!(a.is_legal_diagonal(&offset, &b), "{a:?} {offset:?} {b:?}");
722    }
723}