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(Default, Clone, Debug, Visit)]
57pub struct OrthoTransformMap<V> {
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({})(flipped)", rotation)
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> 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}
318impl<V> OrthoTransform for OrthoTransformMap<V> {
319    fn x_flipped(self) -> Self {
320        Self {
321            transform: self.transform.x_flipped(),
322            map: self.map,
323        }
324    }
325
326    fn rotated(self, amount: i8) -> Self {
327        Self {
328            transform: self.transform.rotated(amount),
329            map: self.map,
330        }
331    }
332}
333/// Iterator for [`OrthoTransformMap`].
334pub struct Iter<'a, V>(
335    OrthoTransformation,
336    std::collections::hash_map::Iter<'a, Vector2<i32>, V>,
337);
338
339/// Keys iterator for [`OrthoTransformMap`].
340pub struct Keys<'a, V>(
341    OrthoTransformation,
342    std::collections::hash_map::Keys<'a, Vector2<i32>, V>,
343);
344
345impl<'a, V> Iterator for Iter<'a, V> {
346    type Item = (Vector2<i32>, &'a V);
347
348    fn next(&mut self) -> Option<Self::Item> {
349        let (k, v) = self.1.next()?;
350        Some((k.transformed(self.0), v))
351    }
352}
353impl<V> Iterator for Keys<'_, V> {
354    type Item = Vector2<i32>;
355
356    fn next(&mut self) -> Option<Self::Item> {
357        let k = self.1.next()?;
358        Some(k.transformed(self.0))
359    }
360}
361
362/// TransformSetCell represents a position within a transform set page and the corresponding transform.
363/// This object fascilitates using a transform set page to transform tiles by moving between positions
364/// in the page based on the desired transformation.
365#[derive(Clone, Copy, Debug, PartialEq, Eq)]
366pub struct TransformSetCell(Vector2<i32>, OrthoTransformation);
367
368fn transform_to_cell_position(rotation: i8) -> Vector2<i32> {
369    match rotation {
370        -1 => Vector2::new(3, 0),
371        -2 => Vector2::new(3, 1),
372        -3 => Vector2::new(2, 1),
373        -4 => Vector2::new(2, 0),
374        1 => Vector2::new(0, 0),
375        2 => Vector2::new(1, 0),
376        3 => Vector2::new(1, 1),
377        4 => Vector2::new(0, 1),
378        _ => panic!(),
379    }
380}
381
382fn cell_position_to_transform(position: Vector2<i32>) -> i8 {
383    match (position.x, position.y) {
384        (3, 0) => -1,
385        (3, 1) => -2,
386        (2, 1) => -3,
387        (2, 0) => -4,
388        (0, 0) => 1,
389        (1, 0) => 2,
390        (1, 1) => 3,
391        (0, 1) => 4,
392        _ => panic!(),
393    }
394}
395
396impl TransformSetCell {
397    /// Convert this cell into the corresponding position within a TransformSet page.
398    pub fn into_position(self) -> Vector2<i32> {
399        self.0 + transform_to_cell_position(self.1 .0)
400    }
401    /// Convert a position within a TransformSet into a transform set cell that specifies the corresponding orthogonal transformation.
402    pub fn from_position(position: Vector2<i32>) -> Self {
403        let rem = Vector2::new(position.x.rem_euclid(4), position.y.rem_euclid(2));
404        let pos = Vector2::new(position.x - rem.x, position.y - rem.y);
405        TransformSetCell(pos, OrthoTransformation(cell_position_to_transform(rem)))
406    }
407    /// Replace the transformation of this cell with a different transformation.
408    /// This is part of the process of rotating a tile:
409    /// 1. Find the transform set [`TileDefinitionHandle`](super::TileDefinitionHandle) from the tile's data.
410    /// 2. Convert the tile position from the `TileDefinitonHandle` into a `TransformSetCell` using [TransformSetCell::from_position].
411    /// 3. Use `with_transformation` to replace the transformation from the `TransformSetCell` with the desired transformation.
412    /// 4. Convert the updated `TransformSetCell` into a tile position using [TransformSetCell::into_position].
413    /// 5. Get the `TileDefinitionHandle` from the transform set page at that position.
414    pub fn with_transformation(self, trans: OrthoTransformation) -> Self {
415        TransformSetCell(self.0, trans)
416    }
417}
418
419impl OrthoTransform for TransformSetCell {
420    fn x_flipped(self) -> Self {
421        Self(self.0, self.1.x_flipped())
422    }
423
424    fn rotated(self, amount: i8) -> Self {
425        Self(self.0, self.1.rotated(amount))
426    }
427}
428
429#[cfg(test)]
430mod tests {
431    use super::*;
432    use crate::core::algebra::Point2;
433    use OrthoTransformation as Trans;
434    use TransformSetCell as Cell;
435
436    #[test]
437    fn identity() {
438        assert_eq!(Trans::identity(), Trans::new(false, 0));
439        for v in [
440            Vector2::new(2, 1),
441            Vector2::new(1, 2),
442            Vector2::new(-1, 5),
443            Vector2::new(-2, -2),
444        ] {
445            assert_eq!(v.transformed(Trans::identity()), v);
446        }
447    }
448    fn matrix_check(trans: OrthoTransformation) {
449        let v = Vector2::new(1.0, 0.5);
450        let m = trans.matrix().to_homogeneous();
451        let p = m.transform_point(&Point2::from(v)).coords;
452        assert_eq!(p, v.transformed(trans), "{}", trans);
453    }
454    #[test]
455    fn matrix() {
456        for i in 0..4 {
457            matrix_check(Trans::new(false, i))
458        }
459        for i in 0..4 {
460            matrix_check(Trans::new(true, i))
461        }
462    }
463    #[test]
464    fn rotate_4() {
465        assert_eq!(Trans::identity(), Trans::new(false, 4))
466    }
467    #[test]
468    fn invert() {
469        for i in [-4, -3, -2, -1, 1, 2, 3, 4] {
470            assert_eq!(
471                Trans(i).transformed(Trans(i).inverted()),
472                Trans::identity(),
473                "{:?}: {:?} {:?}",
474                i,
475                Trans(i),
476                Trans(i).inverted()
477            );
478        }
479    }
480    #[test]
481    fn inverse_undoes_transform() {
482        for i in [-4, -3, -2, -1, 1, 2, 3, 4] {
483            assert_eq!(
484                Vector2::new(2, 3)
485                    .transformed(Trans(i))
486                    .transformed(Trans(i).inverted()),
487                Vector2::new(2, 3),
488            );
489        }
490    }
491    #[test]
492    fn rotate_trans() {
493        assert_eq!(Trans::new(false, 0).rotated(0), Trans::new(false, 0));
494        assert_eq!(Trans::new(true, 0).rotated(0), Trans::new(true, 0));
495        assert_eq!(Trans::new(true, 2).rotated(0), Trans::new(true, 2));
496        assert_eq!(Trans::new(false, 0).rotated(1), Trans::new(false, 1));
497        assert_eq!(Trans::new(true, 0).rotated(1), Trans::new(true, 1));
498        assert_eq!(Trans::new(true, 2).rotated(1), Trans::new(true, 3));
499        assert_eq!(Trans::new(false, 0).rotated(2), Trans::new(false, 2));
500        assert_eq!(Trans::new(true, 0).rotated(2), Trans::new(true, 2));
501        assert_eq!(Trans::new(true, 2).rotated(2), Trans::new(true, 0));
502    }
503    #[test]
504    fn flipped_trans() {
505        assert_eq!(Trans::new(false, 0).x_flipped(), Trans::new(true, 0));
506        assert_eq!(Trans::new(true, 0).x_flipped(), Trans::new(false, 0));
507        assert_eq!(Trans::new(true, 2).x_flipped(), Trans::new(false, 2));
508        assert_eq!(Trans::new(true, 1).x_flipped(), Trans::new(false, 3));
509        assert_eq!(Trans::new(false, 0).y_flipped(), Trans::new(true, 2));
510        assert_eq!(Trans::new(true, 0).y_flipped(), Trans::new(false, 2));
511        assert_eq!(Trans::new(true, 2).y_flipped(), Trans::new(false, 0));
512        assert_eq!(Trans::new(true, 1).y_flipped(), Trans::new(false, 1));
513    }
514    #[test]
515    fn rotate_vector() {
516        assert_eq!(Vector2::new(1, 0).rotated(0), Vector2::new(1, 0));
517        assert_eq!(Vector2::new(0, 1).rotated(0), Vector2::new(0, 1));
518        assert_eq!(Vector2::new(2, 3).rotated(0), Vector2::new(2, 3));
519        assert_eq!(Vector2::new(1, 0).rotated(1), Vector2::new(0, 1));
520        assert_eq!(Vector2::new(0, 1).rotated(1), Vector2::new(-1, 0));
521        assert_eq!(Vector2::new(2, 3).rotated(1), Vector2::new(-3, 2));
522        assert_eq!(Vector2::new(1, 0).rotated(2), Vector2::new(-1, 0));
523        assert_eq!(Vector2::new(0, 1).rotated(2), Vector2::new(0, -1));
524        assert_eq!(Vector2::new(2, 3).rotated(2), Vector2::new(-2, -3));
525        assert_eq!(Vector2::new(1, 0).rotated(3), Vector2::new(0, -1));
526        assert_eq!(Vector2::new(0, 1).rotated(3), Vector2::new(1, 0));
527        assert_eq!(Vector2::new(2, 3).rotated(3), Vector2::new(3, -2));
528        assert_eq!(Vector2::new(1, 0).rotated(4), Vector2::new(1, 0));
529        assert_eq!(Vector2::new(0, 1).rotated(4), Vector2::new(0, 1));
530        assert_eq!(Vector2::new(2, 3).rotated(4), Vector2::new(2, 3));
531    }
532    #[test]
533    fn flipped_vector() {
534        assert_eq!(Vector2::new(1, 0).x_flipped(), Vector2::new(-1, 0));
535        assert_eq!(Vector2::new(0, 1).x_flipped(), Vector2::new(0, 1));
536        assert_eq!(Vector2::new(2, 3).x_flipped(), Vector2::new(-2, 3));
537        assert_eq!(Vector2::new(1, 0).y_flipped(), Vector2::new(1, 0));
538        assert_eq!(Vector2::new(0, 1).y_flipped(), Vector2::new(0, -1));
539        assert_eq!(Vector2::new(2, 3).y_flipped(), Vector2::new(2, -3));
540    }
541    #[test]
542    fn flipped() {
543        assert!(!Trans::new(false, -3).is_flipped());
544        assert!(!Trans::new(false, -2).is_flipped());
545        assert!(!Trans::new(false, -1).is_flipped());
546        assert!(!Trans::new(false, 0).is_flipped());
547        assert!(!Trans::new(false, 1).is_flipped());
548        assert!(!Trans::new(false, 2).is_flipped());
549        assert!(!Trans::new(false, 3).is_flipped());
550        assert!(!Trans::new(false, 4).is_flipped());
551        assert!(!Trans::new(false, 5).is_flipped());
552        assert!(Trans::new(true, -3).is_flipped());
553        assert!(Trans::new(true, -2).is_flipped());
554        assert!(Trans::new(true, -1).is_flipped());
555        assert!(Trans::new(true, 0).is_flipped());
556        assert!(Trans::new(true, 1).is_flipped());
557        assert!(Trans::new(true, 2).is_flipped());
558        assert!(Trans::new(true, 3).is_flipped());
559        assert!(Trans::new(true, 4).is_flipped());
560        assert!(Trans::new(true, 5).is_flipped());
561    }
562    #[test]
563    fn rotate_amount() {
564        assert_eq!(Trans::new(false, -3).rotation(), 1);
565        assert_eq!(Trans::new(false, -2).rotation(), 2);
566        assert_eq!(Trans::new(false, -1).rotation(), 3);
567        assert_eq!(Trans::new(false, 0).rotation(), 0);
568        assert_eq!(Trans::new(false, 1).rotation(), 1);
569        assert_eq!(Trans::new(false, 2).rotation(), 2);
570        assert_eq!(Trans::new(false, 3).rotation(), 3);
571        assert_eq!(Trans::new(false, 4).rotation(), 0);
572        assert_eq!(Trans::new(false, 5).rotation(), 1);
573    }
574    #[test]
575    fn flipped_rotate_amount() {
576        assert_eq!(Trans::new(true, -3).rotation(), 1);
577        assert_eq!(Trans::new(true, -2).rotation(), 2);
578        assert_eq!(Trans::new(true, -1).rotation(), 3);
579        assert_eq!(Trans::new(true, 0).rotation(), 0);
580        assert_eq!(Trans::new(true, 1).rotation(), 1);
581        assert_eq!(Trans::new(true, 2).rotation(), 2);
582        assert_eq!(Trans::new(true, 3).rotation(), 3);
583        assert_eq!(Trans::new(true, 4).rotation(), 0);
584        assert_eq!(Trans::new(true, 5).rotation(), 1);
585    }
586    #[test]
587    fn double_x_flip() {
588        assert_eq!(Trans::identity(), Trans::identity().x_flipped().x_flipped())
589    }
590    #[test]
591    fn double_y_flip() {
592        assert_eq!(Trans::identity(), Trans::identity().y_flipped().y_flipped())
593    }
594    #[test]
595    fn cell_from_position_0() {
596        assert_eq!(
597            Cell::from_position(Vector2::new(0, 0)),
598            Cell(Vector2::new(0, 0), Trans::identity())
599        );
600    }
601    #[test]
602    fn cell_from_position_1() {
603        assert_eq!(
604            Cell::from_position(Vector2::new(1, 0)),
605            Cell(Vector2::new(0, 0), Trans::identity().rotated(1))
606        );
607    }
608    #[test]
609    fn cell_from_position_2() {
610        assert_eq!(
611            Cell::from_position(Vector2::new(2, 0)),
612            Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(3))
613        );
614    }
615    #[test]
616    fn cell_from_position_negative_0() {
617        assert_eq!(
618            Cell::from_position(Vector2::new(0, -2)),
619            Cell(Vector2::new(0, -2), Trans::identity())
620        );
621    }
622    #[test]
623    fn cell_into_position_0() {
624        assert_eq!(
625            Cell(Vector2::new(0, 0), Trans::identity().rotated(0)).into_position(),
626            Vector2::new(0, 0),
627        );
628        assert_eq!(
629            Cell(Vector2::new(0, 0), Trans::identity().rotated(1)).into_position(),
630            Vector2::new(1, 0),
631        );
632        assert_eq!(
633            Cell(Vector2::new(0, 0), Trans::identity().rotated(2)).into_position(),
634            Vector2::new(1, 1),
635        );
636        assert_eq!(
637            Cell(Vector2::new(0, 0), Trans::identity().rotated(3)).into_position(),
638            Vector2::new(0, 1),
639        );
640    }
641    #[test]
642    fn cell_into_position_1() {
643        assert_eq!(
644            Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(0)).into_position(),
645            Vector2::new(3, 0),
646        );
647        assert_eq!(
648            Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(1)).into_position(),
649            Vector2::new(3, 1),
650        );
651        assert_eq!(
652            Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(2)).into_position(),
653            Vector2::new(2, 1),
654        );
655        assert_eq!(
656            Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(3)).into_position(),
657            Vector2::new(2, 0),
658        );
659    }
660    #[test]
661    fn cell_into_position_negative_0() {
662        assert_eq!(
663            Cell(Vector2::new(0, -2), Trans::identity().rotated(0)).into_position(),
664            Vector2::new(0, -2),
665        );
666        assert_eq!(
667            Cell(Vector2::new(0, -2), Trans::identity().rotated(1)).into_position(),
668            Vector2::new(1, -2),
669        );
670        assert_eq!(
671            Cell(Vector2::new(0, -2), Trans::identity().rotated(2)).into_position(),
672            Vector2::new(1, -1),
673        );
674        assert_eq!(
675            Cell(Vector2::new(0, -2), Trans::identity().rotated(3)).into_position(),
676            Vector2::new(0, -1),
677        );
678    }
679    #[test]
680    fn cell_uniqueness() {
681        let mut set = fxhash::FxHashSet::<Vector2<i32>>::default();
682        for t in Trans::all() {
683            set.insert(Cell(Vector2::new(0, 0), t).into_position());
684        }
685        assert_eq!(set.len(), 8, "{set:?}");
686    }
687    #[test]
688    fn cell_correctness() {
689        for x in 0..4 {
690            for y in 0..2 {
691                assert_eq!(
692                    Cell::from_position(Vector2::new(x, y)).0,
693                    Vector2::new(0, 0),
694                    "{x}, {y}"
695                );
696            }
697        }
698        for x in 4..8 {
699            for y in -2..0 {
700                assert_eq!(
701                    Cell::from_position(Vector2::new(x, y)).0,
702                    Vector2::new(4, -2),
703                    "{x}, {y}"
704                );
705            }
706        }
707    }
708    #[test]
709    fn cell_from_position_and_back() {
710        for x in -3..4 {
711            for y in -2..4 {
712                let p = Vector2::new(x, y);
713                assert_eq!(
714                    Cell::from_position(p).into_position(),
715                    p,
716                    "Cell: {:?}",
717                    Cell::from_position(p)
718                );
719            }
720        }
721    }
722}