Skip to main content

fyrox_impl/scene/tilemap/
transform.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//! Orthogonal transformations are some combination of 90-degree turns and
22//! flips across the x-axis and y-axis. These transformations are important to
23//! tiles because they are constrained along the tile grid.
24//!
25//! [`OrthoTransformation`] represents some combination of flips and turns,
26//! and [`OrthoTransform`] is a class for objects that can be transformed.
27
28use std::{
29    fmt::{Debug, Display},
30    ops::{Add, AddAssign, Neg},
31};
32
33use fxhash::FxHashMap;
34
35use crate::core::{
36    algebra::{Matrix2, Scalar, SimdPartialOrd, Vector2},
37    math::{Number, Rect},
38    reflect::prelude::*,
39    visitor::prelude::*,
40};
41
42use super::OptionTileRect;
43
44/// The amount of an orthogonal transformation.
45/// The tranformation either includes an optional initial x-flip that turns (1,0) into (-1,0), or not.
46/// Following the optional flip, the object being transformed is rotated counter-clockwise by some number
47/// of right-angle turns: 0, 1, 2, or 3. Multiple transformations can be chained together for arbitrary
48/// sequences of x-flips, y-flis, clockwise and counter-clockwise rotations.
49///
50/// These transformations are useful in situations where positions are
51/// restricted to an orthogonal grid, as in a tile map.
52#[derive(Copy, Clone, Hash, Eq, PartialEq, Visit, Reflect)]
53pub struct OrthoTransformation(i8);
54
55/// A map from `Vector2<i32>` to values. It can be transformed to flip and rotate the positions of the values.
56#[derive(Clone, Debug, Visit)]
57pub struct OrthoTransformMap<V: Visit + Default> {
58    transform: OrthoTransformation,
59    map: FxHashMap<Vector2<i32>, V>,
60}
61
62impl Default for OrthoTransformation {
63    fn default() -> Self {
64        Self::identity()
65    }
66}
67
68const ROTATION_MATRICES: [Matrix2<f32>; 4] = [
69    Matrix2::new(1.0, 0.0, 0.0, 1.0),
70    Matrix2::new(0.0, -1.0, 1.0, 0.0),
71    Matrix2::new(-1.0, 0.0, 0.0, -1.0),
72    Matrix2::new(0.0, 1.0, -1.0, 0.0),
73];
74
75const X_FLIP_MATRIX: Matrix2<f32> = Matrix2::new(-1.0, 0.0, 0.0, 1.0);
76
77impl OrthoTransformation {
78    /// The transform that does nothing. It has no flip, and rotates by 0 right-angle turns.
79    #[inline]
80    pub const fn identity() -> Self {
81        Self(1)
82    }
83    /// The transformation that first does an optional x-flip,
84    /// then rotates counter-clockwise by the given amount.
85    /// The rotation is measured in units of 90-degree rotations.
86    /// Positive rotation is counter-clockwise. Negative rotation is clockwise.
87    #[inline]
88    pub const fn new(flipped: bool, rotation: i8) -> Self {
89        let rotation = rotation.rem_euclid(4);
90        Self(if flipped { -rotation - 1 } else { rotation + 1 })
91    }
92    /// An iterator over all 8 possible OrthoTransformations.
93    pub fn all() -> impl Iterator<Item = OrthoTransformation> {
94        [-4i8, -3, -2, -1, 1, 2, 3, 4]
95            .into_iter()
96            .map(OrthoTransformation)
97    }
98    /// True if this tranformation is the idenity transformation that leaves the transformed object unmodified.
99    #[inline]
100    pub const fn is_identity(&self) -> bool {
101        self.0 == 1
102    }
103    /// Reverse this transformation, to it would return a transformed object
104    /// back to where it started.
105    /// In other words: `x.transformed(t).transformed(t.inverted()) == x`.
106    #[inline]
107    pub const fn inverted(self) -> Self {
108        Self(match self.0 {
109            1 => 1,
110            2 => 4,
111            3 => 3,
112            4 => 2,
113            -1 => -1,
114            -2 => -2,
115            -3 => -3,
116            -4 => -4,
117            _ => unreachable!(),
118        })
119    }
120    /// True if this transform starts with an x-flip.
121    #[inline]
122    pub const fn is_flipped(&self) -> bool {
123        self.0 < 0
124    }
125    /// The amount of rotation following the optional x-flip.
126    /// The value is always 0, 1, 2, or 3, representing counter-clockwise rotations of
127    /// 0, 90, 180, or 270 degrees.
128    #[inline]
129    pub const fn rotation(&self) -> i8 {
130        self.0.abs() - 1
131    }
132    /// Matrix representation of this transformation.
133    pub fn matrix(&self) -> Matrix2<f32> {
134        let matrix = if self.is_flipped() {
135            X_FLIP_MATRIX
136        } else {
137            Matrix2::identity()
138        };
139        ROTATION_MATRICES[self.rotation() as usize] * matrix
140    }
141}
142
143impl Debug for OrthoTransformation {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        f.debug_tuple("OrthoTransformation")
146            .field(&self.0)
147            .field(&self.is_flipped())
148            .field(&self.rotation())
149            .finish()
150    }
151}
152
153impl Display for OrthoTransformation {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        let rotation = match self.rotation() {
156            0 => 0,
157            1 => 90,
158            2 => 180,
159            3 => 270,
160            _ => unreachable!(),
161        };
162        if self.is_flipped() {
163            write!(f, "rotate({rotation})(flipped)")
164        } else {
165            write!(f, "rotate({rotation})")
166        }
167    }
168}
169
170/// Trait for objects that can perform a 2D orthogonal transformation.
171/// In other words, they can be flipped along the x and y axis,
172/// and they can be rotated by multiples of 90 degrees.
173pub trait OrthoTransform: Sized {
174    /// Flip the object parallel to the x axis, so x becomes -x.
175    fn x_flipped(self) -> Self;
176    /// Flip the object parallel to the y axis, so y becomes -y.
177    fn y_flipped(self) -> Self {
178        self.x_flipped().rotated(2)
179    }
180    /// Rotate the object counter-clockwise by the given amount.
181    fn rotated(self, amount: i8) -> Self;
182    /// Transform the object by the given amount.
183    fn transformed(self, amount: OrthoTransformation) -> Self {
184        (if amount.is_flipped() {
185            self.x_flipped()
186        } else {
187            self
188        })
189        .rotated(amount.rotation())
190    }
191}
192
193impl OrthoTransform for OrthoTransformation {
194    fn x_flipped(self) -> Self {
195        Self(match self.0 {
196            1 => -1,
197            2 => -4,
198            3 => -3,
199            4 => -2,
200            -1 => 1,
201            -2 => 4,
202            -3 => 3,
203            -4 => 2,
204            _ => unreachable!(),
205        })
206    }
207    fn rotated(self, amount: i8) -> Self {
208        let amount = amount.rem_euclid(4);
209        if self.0 > 0 {
210            Self((self.0 + amount - 1).rem_euclid(4) + 1)
211        } else {
212            Self(-(self.0.abs() + amount - 1).rem_euclid(4) - 1)
213        }
214    }
215}
216
217impl<V: Neg<Output = V> + Scalar + Clone> OrthoTransform for Vector2<V> {
218    fn x_flipped(self) -> Self {
219        Self::new(-self.x.clone(), self.y.clone())
220    }
221
222    fn rotated(self, amount: i8) -> Self {
223        let amount = amount.rem_euclid(4);
224        match amount {
225            0 => self,
226            1 => Self::new(-self.y.clone(), self.x.clone()),
227            2 => Self::new(-self.x.clone(), -self.y.clone()),
228            3 => Self::new(self.y.clone(), -self.x.clone()),
229            _ => unreachable!(),
230        }
231    }
232}
233
234impl<V: Number + SimdPartialOrd + Add + AddAssign + Neg<Output = V> + Scalar> OrthoTransform
235    for Rect<V>
236{
237    fn x_flipped(self) -> Self {
238        Rect::from_points(
239            self.position.x_flipped(),
240            (self.position + self.size).x_flipped(),
241        )
242    }
243
244    fn rotated(self, amount: i8) -> Self {
245        Rect::from_points(
246            self.position.rotated(amount),
247            (self.position + self.size).rotated(amount),
248        )
249    }
250}
251
252impl<V: Visit + Default> OrthoTransformMap<V> {
253    /// Bounding rectangle the contains the keys.
254    pub fn bounding_rect(&self) -> OptionTileRect {
255        let mut result = OptionTileRect::default();
256        for position in self.keys() {
257            result.push(position);
258        }
259        result
260    }
261
262    /// Clear the elements of the map.
263    #[inline]
264    pub fn clear(&mut self) {
265        self.map.clear()
266    }
267    /// True if the map contains no elements.
268    #[inline]
269    pub fn is_empty(&self) -> bool {
270        self.map.is_empty()
271    }
272    /// True if the map contains an element at the given position.
273    #[inline]
274    pub fn contains_key(&self, position: Vector2<i32>) -> bool {
275        self.map
276            .contains_key(&position.transformed(self.transform.inverted()))
277    }
278    /// Remove and return the element at the given position, if one exists.
279    #[inline]
280    pub fn remove(&mut self, position: Vector2<i32>) -> Option<V> {
281        self.map
282            .remove(&position.transformed(self.transform.inverted()))
283    }
284    /// Return a reference to the element at the given position, if any.
285    #[inline]
286    pub fn get(&self, position: Vector2<i32>) -> Option<&V> {
287        self.map
288            .get(&position.transformed(self.transform.inverted()))
289    }
290    /// Return a reference to the element at the given position, if any.
291    #[inline]
292    pub fn get_mut(&mut self, position: Vector2<i32>) -> Option<&mut V> {
293        self.map
294            .get_mut(&position.transformed(self.transform.inverted()))
295    }
296    /// Put an element into the map at the given position, and return the element that was previously at that position.
297    #[inline]
298    pub fn insert(&mut self, position: Vector2<i32>, value: V) -> Option<V> {
299        self.map
300            .insert(position.transformed(self.transform.inverted()), value)
301    }
302    /// Iterate through the map.
303    #[inline]
304    pub fn iter(&self) -> Iter<V> {
305        Iter(self.transform, self.map.iter())
306    }
307    /// Iterate through the keys.
308    #[inline]
309    pub fn keys(&self) -> Keys<V> {
310        Keys(self.transform, self.map.keys())
311    }
312    /// Iterate through the values.
313    #[inline]
314    pub fn values(&self) -> std::collections::hash_map::Values<Vector2<i32>, V> {
315        self.map.values()
316    }
317}
318
319impl<V: Visit + Default> Default for OrthoTransformMap<V> {
320    fn default() -> Self {
321        Self {
322            transform: Default::default(),
323            map: Default::default(),
324        }
325    }
326}
327
328impl<V: Visit + Default> OrthoTransform for OrthoTransformMap<V> {
329    fn x_flipped(self) -> Self {
330        Self {
331            transform: self.transform.x_flipped(),
332            map: self.map,
333        }
334    }
335
336    fn rotated(self, amount: i8) -> Self {
337        Self {
338            transform: self.transform.rotated(amount),
339            map: self.map,
340        }
341    }
342}
343/// Iterator for [`OrthoTransformMap`].
344pub struct Iter<'a, V>(
345    OrthoTransformation,
346    std::collections::hash_map::Iter<'a, Vector2<i32>, V>,
347);
348
349/// Keys iterator for [`OrthoTransformMap`].
350pub struct Keys<'a, V>(
351    OrthoTransformation,
352    std::collections::hash_map::Keys<'a, Vector2<i32>, V>,
353);
354
355impl<'a, V> Iterator for Iter<'a, V> {
356    type Item = (Vector2<i32>, &'a V);
357
358    fn next(&mut self) -> Option<Self::Item> {
359        let (k, v) = self.1.next()?;
360        Some((k.transformed(self.0), v))
361    }
362}
363impl<V> Iterator for Keys<'_, V> {
364    type Item = Vector2<i32>;
365
366    fn next(&mut self) -> Option<Self::Item> {
367        let k = self.1.next()?;
368        Some(k.transformed(self.0))
369    }
370}
371
372/// TransformSetCell represents a position within a transform set page and the corresponding transform.
373/// This object fascilitates using a transform set page to transform tiles by moving between positions
374/// in the page based on the desired transformation.
375#[derive(Clone, Copy, Debug, PartialEq, Eq)]
376pub struct TransformSetCell(Vector2<i32>, OrthoTransformation);
377
378fn transform_to_cell_position(rotation: i8) -> Vector2<i32> {
379    match rotation {
380        -1 => Vector2::new(3, 0),
381        -2 => Vector2::new(3, 1),
382        -3 => Vector2::new(2, 1),
383        -4 => Vector2::new(2, 0),
384        1 => Vector2::new(0, 0),
385        2 => Vector2::new(1, 0),
386        3 => Vector2::new(1, 1),
387        4 => Vector2::new(0, 1),
388        _ => panic!(),
389    }
390}
391
392fn cell_position_to_transform(position: Vector2<i32>) -> i8 {
393    match (position.x, position.y) {
394        (3, 0) => -1,
395        (3, 1) => -2,
396        (2, 1) => -3,
397        (2, 0) => -4,
398        (0, 0) => 1,
399        (1, 0) => 2,
400        (1, 1) => 3,
401        (0, 1) => 4,
402        _ => panic!(),
403    }
404}
405
406impl TransformSetCell {
407    /// Convert this cell into the corresponding position within a TransformSet page.
408    pub fn into_position(self) -> Vector2<i32> {
409        self.0 + transform_to_cell_position(self.1 .0)
410    }
411    /// Convert a position within a TransformSet into a transform set cell that specifies the corresponding orthogonal transformation.
412    pub fn from_position(position: Vector2<i32>) -> Self {
413        let rem = Vector2::new(position.x.rem_euclid(4), position.y.rem_euclid(2));
414        let pos = Vector2::new(position.x - rem.x, position.y - rem.y);
415        TransformSetCell(pos, OrthoTransformation(cell_position_to_transform(rem)))
416    }
417    /// Replace the transformation of this cell with a different transformation.
418    /// This is part of the process of rotating a tile:
419    /// 1. Find the transform set [`TileDefinitionHandle`](super::TileDefinitionHandle) from the tile's data.
420    /// 2. Convert the tile position from the `TileDefinitonHandle` into a `TransformSetCell` using [TransformSetCell::from_position].
421    /// 3. Use `with_transformation` to replace the transformation from the `TransformSetCell` with the desired transformation.
422    /// 4. Convert the updated `TransformSetCell` into a tile position using [TransformSetCell::into_position].
423    /// 5. Get the `TileDefinitionHandle` from the transform set page at that position.
424    pub fn with_transformation(self, trans: OrthoTransformation) -> Self {
425        TransformSetCell(self.0, trans)
426    }
427}
428
429impl OrthoTransform for TransformSetCell {
430    fn x_flipped(self) -> Self {
431        Self(self.0, self.1.x_flipped())
432    }
433
434    fn rotated(self, amount: i8) -> Self {
435        Self(self.0, self.1.rotated(amount))
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442    use crate::core::algebra::Point2;
443    use OrthoTransformation as Trans;
444    use TransformSetCell as Cell;
445
446    #[test]
447    fn identity() {
448        assert_eq!(Trans::identity(), Trans::new(false, 0));
449        for v in [
450            Vector2::new(2, 1),
451            Vector2::new(1, 2),
452            Vector2::new(-1, 5),
453            Vector2::new(-2, -2),
454        ] {
455            assert_eq!(v.transformed(Trans::identity()), v);
456        }
457    }
458    fn matrix_check(trans: OrthoTransformation) {
459        let v = Vector2::new(1.0, 0.5);
460        let m = trans.matrix().to_homogeneous();
461        let p = m.transform_point(&Point2::from(v)).coords;
462        assert_eq!(p, v.transformed(trans), "{trans}");
463    }
464    #[test]
465    fn matrix() {
466        for i in 0..4 {
467            matrix_check(Trans::new(false, i))
468        }
469        for i in 0..4 {
470            matrix_check(Trans::new(true, i))
471        }
472    }
473    #[test]
474    fn rotate_4() {
475        assert_eq!(Trans::identity(), Trans::new(false, 4))
476    }
477    #[test]
478    fn invert() {
479        for i in [-4, -3, -2, -1, 1, 2, 3, 4] {
480            assert_eq!(
481                Trans(i).transformed(Trans(i).inverted()),
482                Trans::identity(),
483                "{:?}: {:?} {:?}",
484                i,
485                Trans(i),
486                Trans(i).inverted()
487            );
488        }
489    }
490    #[test]
491    fn inverse_undoes_transform() {
492        for i in [-4, -3, -2, -1, 1, 2, 3, 4] {
493            assert_eq!(
494                Vector2::new(2, 3)
495                    .transformed(Trans(i))
496                    .transformed(Trans(i).inverted()),
497                Vector2::new(2, 3),
498            );
499        }
500    }
501    #[test]
502    fn rotate_trans() {
503        assert_eq!(Trans::new(false, 0).rotated(0), Trans::new(false, 0));
504        assert_eq!(Trans::new(true, 0).rotated(0), Trans::new(true, 0));
505        assert_eq!(Trans::new(true, 2).rotated(0), Trans::new(true, 2));
506        assert_eq!(Trans::new(false, 0).rotated(1), Trans::new(false, 1));
507        assert_eq!(Trans::new(true, 0).rotated(1), Trans::new(true, 1));
508        assert_eq!(Trans::new(true, 2).rotated(1), Trans::new(true, 3));
509        assert_eq!(Trans::new(false, 0).rotated(2), Trans::new(false, 2));
510        assert_eq!(Trans::new(true, 0).rotated(2), Trans::new(true, 2));
511        assert_eq!(Trans::new(true, 2).rotated(2), Trans::new(true, 0));
512    }
513    #[test]
514    fn flipped_trans() {
515        assert_eq!(Trans::new(false, 0).x_flipped(), Trans::new(true, 0));
516        assert_eq!(Trans::new(true, 0).x_flipped(), Trans::new(false, 0));
517        assert_eq!(Trans::new(true, 2).x_flipped(), Trans::new(false, 2));
518        assert_eq!(Trans::new(true, 1).x_flipped(), Trans::new(false, 3));
519        assert_eq!(Trans::new(false, 0).y_flipped(), Trans::new(true, 2));
520        assert_eq!(Trans::new(true, 0).y_flipped(), Trans::new(false, 2));
521        assert_eq!(Trans::new(true, 2).y_flipped(), Trans::new(false, 0));
522        assert_eq!(Trans::new(true, 1).y_flipped(), Trans::new(false, 1));
523    }
524    #[test]
525    fn rotate_vector() {
526        assert_eq!(Vector2::new(1, 0).rotated(0), Vector2::new(1, 0));
527        assert_eq!(Vector2::new(0, 1).rotated(0), Vector2::new(0, 1));
528        assert_eq!(Vector2::new(2, 3).rotated(0), Vector2::new(2, 3));
529        assert_eq!(Vector2::new(1, 0).rotated(1), Vector2::new(0, 1));
530        assert_eq!(Vector2::new(0, 1).rotated(1), Vector2::new(-1, 0));
531        assert_eq!(Vector2::new(2, 3).rotated(1), Vector2::new(-3, 2));
532        assert_eq!(Vector2::new(1, 0).rotated(2), Vector2::new(-1, 0));
533        assert_eq!(Vector2::new(0, 1).rotated(2), Vector2::new(0, -1));
534        assert_eq!(Vector2::new(2, 3).rotated(2), Vector2::new(-2, -3));
535        assert_eq!(Vector2::new(1, 0).rotated(3), Vector2::new(0, -1));
536        assert_eq!(Vector2::new(0, 1).rotated(3), Vector2::new(1, 0));
537        assert_eq!(Vector2::new(2, 3).rotated(3), Vector2::new(3, -2));
538        assert_eq!(Vector2::new(1, 0).rotated(4), Vector2::new(1, 0));
539        assert_eq!(Vector2::new(0, 1).rotated(4), Vector2::new(0, 1));
540        assert_eq!(Vector2::new(2, 3).rotated(4), Vector2::new(2, 3));
541    }
542    #[test]
543    fn flipped_vector() {
544        assert_eq!(Vector2::new(1, 0).x_flipped(), Vector2::new(-1, 0));
545        assert_eq!(Vector2::new(0, 1).x_flipped(), Vector2::new(0, 1));
546        assert_eq!(Vector2::new(2, 3).x_flipped(), Vector2::new(-2, 3));
547        assert_eq!(Vector2::new(1, 0).y_flipped(), Vector2::new(1, 0));
548        assert_eq!(Vector2::new(0, 1).y_flipped(), Vector2::new(0, -1));
549        assert_eq!(Vector2::new(2, 3).y_flipped(), Vector2::new(2, -3));
550    }
551    #[test]
552    fn flipped() {
553        assert!(!Trans::new(false, -3).is_flipped());
554        assert!(!Trans::new(false, -2).is_flipped());
555        assert!(!Trans::new(false, -1).is_flipped());
556        assert!(!Trans::new(false, 0).is_flipped());
557        assert!(!Trans::new(false, 1).is_flipped());
558        assert!(!Trans::new(false, 2).is_flipped());
559        assert!(!Trans::new(false, 3).is_flipped());
560        assert!(!Trans::new(false, 4).is_flipped());
561        assert!(!Trans::new(false, 5).is_flipped());
562        assert!(Trans::new(true, -3).is_flipped());
563        assert!(Trans::new(true, -2).is_flipped());
564        assert!(Trans::new(true, -1).is_flipped());
565        assert!(Trans::new(true, 0).is_flipped());
566        assert!(Trans::new(true, 1).is_flipped());
567        assert!(Trans::new(true, 2).is_flipped());
568        assert!(Trans::new(true, 3).is_flipped());
569        assert!(Trans::new(true, 4).is_flipped());
570        assert!(Trans::new(true, 5).is_flipped());
571    }
572    #[test]
573    fn rotate_amount() {
574        assert_eq!(Trans::new(false, -3).rotation(), 1);
575        assert_eq!(Trans::new(false, -2).rotation(), 2);
576        assert_eq!(Trans::new(false, -1).rotation(), 3);
577        assert_eq!(Trans::new(false, 0).rotation(), 0);
578        assert_eq!(Trans::new(false, 1).rotation(), 1);
579        assert_eq!(Trans::new(false, 2).rotation(), 2);
580        assert_eq!(Trans::new(false, 3).rotation(), 3);
581        assert_eq!(Trans::new(false, 4).rotation(), 0);
582        assert_eq!(Trans::new(false, 5).rotation(), 1);
583    }
584    #[test]
585    fn flipped_rotate_amount() {
586        assert_eq!(Trans::new(true, -3).rotation(), 1);
587        assert_eq!(Trans::new(true, -2).rotation(), 2);
588        assert_eq!(Trans::new(true, -1).rotation(), 3);
589        assert_eq!(Trans::new(true, 0).rotation(), 0);
590        assert_eq!(Trans::new(true, 1).rotation(), 1);
591        assert_eq!(Trans::new(true, 2).rotation(), 2);
592        assert_eq!(Trans::new(true, 3).rotation(), 3);
593        assert_eq!(Trans::new(true, 4).rotation(), 0);
594        assert_eq!(Trans::new(true, 5).rotation(), 1);
595    }
596    #[test]
597    fn double_x_flip() {
598        assert_eq!(Trans::identity(), Trans::identity().x_flipped().x_flipped())
599    }
600    #[test]
601    fn double_y_flip() {
602        assert_eq!(Trans::identity(), Trans::identity().y_flipped().y_flipped())
603    }
604    #[test]
605    fn cell_from_position_0() {
606        assert_eq!(
607            Cell::from_position(Vector2::new(0, 0)),
608            Cell(Vector2::new(0, 0), Trans::identity())
609        );
610    }
611    #[test]
612    fn cell_from_position_1() {
613        assert_eq!(
614            Cell::from_position(Vector2::new(1, 0)),
615            Cell(Vector2::new(0, 0), Trans::identity().rotated(1))
616        );
617    }
618    #[test]
619    fn cell_from_position_2() {
620        assert_eq!(
621            Cell::from_position(Vector2::new(2, 0)),
622            Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(3))
623        );
624    }
625    #[test]
626    fn cell_from_position_negative_0() {
627        assert_eq!(
628            Cell::from_position(Vector2::new(0, -2)),
629            Cell(Vector2::new(0, -2), Trans::identity())
630        );
631    }
632    #[test]
633    fn cell_into_position_0() {
634        assert_eq!(
635            Cell(Vector2::new(0, 0), Trans::identity().rotated(0)).into_position(),
636            Vector2::new(0, 0),
637        );
638        assert_eq!(
639            Cell(Vector2::new(0, 0), Trans::identity().rotated(1)).into_position(),
640            Vector2::new(1, 0),
641        );
642        assert_eq!(
643            Cell(Vector2::new(0, 0), Trans::identity().rotated(2)).into_position(),
644            Vector2::new(1, 1),
645        );
646        assert_eq!(
647            Cell(Vector2::new(0, 0), Trans::identity().rotated(3)).into_position(),
648            Vector2::new(0, 1),
649        );
650    }
651    #[test]
652    fn cell_into_position_1() {
653        assert_eq!(
654            Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(0)).into_position(),
655            Vector2::new(3, 0),
656        );
657        assert_eq!(
658            Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(1)).into_position(),
659            Vector2::new(3, 1),
660        );
661        assert_eq!(
662            Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(2)).into_position(),
663            Vector2::new(2, 1),
664        );
665        assert_eq!(
666            Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(3)).into_position(),
667            Vector2::new(2, 0),
668        );
669    }
670    #[test]
671    fn cell_into_position_negative_0() {
672        assert_eq!(
673            Cell(Vector2::new(0, -2), Trans::identity().rotated(0)).into_position(),
674            Vector2::new(0, -2),
675        );
676        assert_eq!(
677            Cell(Vector2::new(0, -2), Trans::identity().rotated(1)).into_position(),
678            Vector2::new(1, -2),
679        );
680        assert_eq!(
681            Cell(Vector2::new(0, -2), Trans::identity().rotated(2)).into_position(),
682            Vector2::new(1, -1),
683        );
684        assert_eq!(
685            Cell(Vector2::new(0, -2), Trans::identity().rotated(3)).into_position(),
686            Vector2::new(0, -1),
687        );
688    }
689    #[test]
690    fn cell_uniqueness() {
691        let mut set = fxhash::FxHashSet::<Vector2<i32>>::default();
692        for t in Trans::all() {
693            set.insert(Cell(Vector2::new(0, 0), t).into_position());
694        }
695        assert_eq!(set.len(), 8, "{set:?}");
696    }
697    #[test]
698    fn cell_correctness() {
699        for x in 0..4 {
700            for y in 0..2 {
701                assert_eq!(
702                    Cell::from_position(Vector2::new(x, y)).0,
703                    Vector2::new(0, 0),
704                    "{x}, {y}"
705                );
706            }
707        }
708        for x in 4..8 {
709            for y in -2..0 {
710                assert_eq!(
711                    Cell::from_position(Vector2::new(x, y)).0,
712                    Vector2::new(4, -2),
713                    "{x}, {y}"
714                );
715            }
716        }
717    }
718    #[test]
719    fn cell_from_position_and_back() {
720        for x in -3..4 {
721            for y in -2..4 {
722                let p = Vector2::new(x, y);
723                assert_eq!(
724                    Cell::from_position(p).into_position(),
725                    p,
726                    "Cell: {:?}",
727                    Cell::from_position(p)
728                );
729            }
730        }
731    }
732}