use std::{
fmt::{Debug, Display},
ops::{Add, AddAssign, Neg},
};
use fxhash::FxHashMap;
use crate::core::{
algebra::{Matrix2, Scalar, SimdPartialOrd, Vector2},
math::{Number, Rect},
reflect::prelude::*,
visitor::prelude::*,
};
use super::OptionTileRect;
#[derive(Copy, Clone, Hash, Eq, PartialEq, Visit, Reflect)]
pub struct OrthoTransformation(i8);
#[derive(Clone, Debug, Visit)]
pub struct OrthoTransformMap<V: Visit + Default> {
transform: OrthoTransformation,
map: FxHashMap<Vector2<i32>, V>,
}
impl Default for OrthoTransformation {
fn default() -> Self {
Self::identity()
}
}
const ROTATION_MATRICES: [Matrix2<f32>; 4] = [
Matrix2::new(1.0, 0.0, 0.0, 1.0),
Matrix2::new(0.0, -1.0, 1.0, 0.0),
Matrix2::new(-1.0, 0.0, 0.0, -1.0),
Matrix2::new(0.0, 1.0, -1.0, 0.0),
];
const X_FLIP_MATRIX: Matrix2<f32> = Matrix2::new(-1.0, 0.0, 0.0, 1.0);
impl OrthoTransformation {
#[inline]
pub const fn identity() -> Self {
Self(1)
}
#[inline]
pub const fn new(flipped: bool, rotation: i8) -> Self {
let rotation = rotation.rem_euclid(4);
Self(if flipped { -rotation - 1 } else { rotation + 1 })
}
pub fn all() -> impl Iterator<Item = OrthoTransformation> {
[-4i8, -3, -2, -1, 1, 2, 3, 4]
.into_iter()
.map(OrthoTransformation)
}
#[inline]
pub const fn is_identity(&self) -> bool {
self.0 == 1
}
#[inline]
pub const fn inverted(self) -> Self {
Self(match self.0 {
1 => 1,
2 => 4,
3 => 3,
4 => 2,
-1 => -1,
-2 => -2,
-3 => -3,
-4 => -4,
_ => unreachable!(),
})
}
#[inline]
pub const fn is_flipped(&self) -> bool {
self.0 < 0
}
#[inline]
pub const fn rotation(&self) -> i8 {
self.0.abs() - 1
}
pub fn matrix(&self) -> Matrix2<f32> {
let matrix = if self.is_flipped() {
X_FLIP_MATRIX
} else {
Matrix2::identity()
};
ROTATION_MATRICES[self.rotation() as usize] * matrix
}
}
impl Debug for OrthoTransformation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("OrthoTransformation")
.field(&self.0)
.field(&self.is_flipped())
.field(&self.rotation())
.finish()
}
}
impl Display for OrthoTransformation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let rotation = match self.rotation() {
0 => 0,
1 => 90,
2 => 180,
3 => 270,
_ => unreachable!(),
};
if self.is_flipped() {
write!(f, "rotate({rotation})(flipped)")
} else {
write!(f, "rotate({rotation})")
}
}
}
pub trait OrthoTransform: Sized {
fn x_flipped(self) -> Self;
fn y_flipped(self) -> Self {
self.x_flipped().rotated(2)
}
fn rotated(self, amount: i8) -> Self;
fn transformed(self, amount: OrthoTransformation) -> Self {
(if amount.is_flipped() {
self.x_flipped()
} else {
self
})
.rotated(amount.rotation())
}
}
impl OrthoTransform for OrthoTransformation {
fn x_flipped(self) -> Self {
Self(match self.0 {
1 => -1,
2 => -4,
3 => -3,
4 => -2,
-1 => 1,
-2 => 4,
-3 => 3,
-4 => 2,
_ => unreachable!(),
})
}
fn rotated(self, amount: i8) -> Self {
let amount = amount.rem_euclid(4);
if self.0 > 0 {
Self((self.0 + amount - 1).rem_euclid(4) + 1)
} else {
Self(-(self.0.abs() + amount - 1).rem_euclid(4) - 1)
}
}
}
impl<V: Neg<Output = V> + Scalar + Clone> OrthoTransform for Vector2<V> {
fn x_flipped(self) -> Self {
Self::new(-self.x.clone(), self.y.clone())
}
fn rotated(self, amount: i8) -> Self {
let amount = amount.rem_euclid(4);
match amount {
0 => self,
1 => Self::new(-self.y.clone(), self.x.clone()),
2 => Self::new(-self.x.clone(), -self.y.clone()),
3 => Self::new(self.y.clone(), -self.x.clone()),
_ => unreachable!(),
}
}
}
impl<V: Number + SimdPartialOrd + Add + AddAssign + Neg<Output = V> + Scalar> OrthoTransform
for Rect<V>
{
fn x_flipped(self) -> Self {
Rect::from_points(
self.position.x_flipped(),
(self.position + self.size).x_flipped(),
)
}
fn rotated(self, amount: i8) -> Self {
Rect::from_points(
self.position.rotated(amount),
(self.position + self.size).rotated(amount),
)
}
}
impl<V: Visit + Default> OrthoTransformMap<V> {
pub fn bounding_rect(&self) -> OptionTileRect {
let mut result = OptionTileRect::default();
for position in self.keys() {
result.push(position);
}
result
}
#[inline]
pub fn clear(&mut self) {
self.map.clear()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
#[inline]
pub fn contains_key(&self, position: Vector2<i32>) -> bool {
self.map
.contains_key(&position.transformed(self.transform.inverted()))
}
#[inline]
pub fn remove(&mut self, position: Vector2<i32>) -> Option<V> {
self.map
.remove(&position.transformed(self.transform.inverted()))
}
#[inline]
pub fn get(&self, position: Vector2<i32>) -> Option<&V> {
self.map
.get(&position.transformed(self.transform.inverted()))
}
#[inline]
pub fn get_mut(&mut self, position: Vector2<i32>) -> Option<&mut V> {
self.map
.get_mut(&position.transformed(self.transform.inverted()))
}
#[inline]
pub fn insert(&mut self, position: Vector2<i32>, value: V) -> Option<V> {
self.map
.insert(position.transformed(self.transform.inverted()), value)
}
#[inline]
pub fn iter(&self) -> Iter<V> {
Iter(self.transform, self.map.iter())
}
#[inline]
pub fn keys(&self) -> Keys<V> {
Keys(self.transform, self.map.keys())
}
#[inline]
pub fn values(&self) -> std::collections::hash_map::Values<Vector2<i32>, V> {
self.map.values()
}
}
impl<V: Visit + Default> Default for OrthoTransformMap<V> {
fn default() -> Self {
Self {
transform: Default::default(),
map: Default::default(),
}
}
}
impl<V: Visit + Default> OrthoTransform for OrthoTransformMap<V> {
fn x_flipped(self) -> Self {
Self {
transform: self.transform.x_flipped(),
map: self.map,
}
}
fn rotated(self, amount: i8) -> Self {
Self {
transform: self.transform.rotated(amount),
map: self.map,
}
}
}
pub struct Iter<'a, V>(
OrthoTransformation,
std::collections::hash_map::Iter<'a, Vector2<i32>, V>,
);
pub struct Keys<'a, V>(
OrthoTransformation,
std::collections::hash_map::Keys<'a, Vector2<i32>, V>,
);
impl<'a, V> Iterator for Iter<'a, V> {
type Item = (Vector2<i32>, &'a V);
fn next(&mut self) -> Option<Self::Item> {
let (k, v) = self.1.next()?;
Some((k.transformed(self.0), v))
}
}
impl<V> Iterator for Keys<'_, V> {
type Item = Vector2<i32>;
fn next(&mut self) -> Option<Self::Item> {
let k = self.1.next()?;
Some(k.transformed(self.0))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TransformSetCell(Vector2<i32>, OrthoTransformation);
fn transform_to_cell_position(rotation: i8) -> Vector2<i32> {
match rotation {
-1 => Vector2::new(3, 0),
-2 => Vector2::new(3, 1),
-3 => Vector2::new(2, 1),
-4 => Vector2::new(2, 0),
1 => Vector2::new(0, 0),
2 => Vector2::new(1, 0),
3 => Vector2::new(1, 1),
4 => Vector2::new(0, 1),
_ => panic!(),
}
}
fn cell_position_to_transform(position: Vector2<i32>) -> i8 {
match (position.x, position.y) {
(3, 0) => -1,
(3, 1) => -2,
(2, 1) => -3,
(2, 0) => -4,
(0, 0) => 1,
(1, 0) => 2,
(1, 1) => 3,
(0, 1) => 4,
_ => panic!(),
}
}
impl TransformSetCell {
pub fn into_position(self) -> Vector2<i32> {
self.0 + transform_to_cell_position(self.1 .0)
}
pub fn from_position(position: Vector2<i32>) -> Self {
let rem = Vector2::new(position.x.rem_euclid(4), position.y.rem_euclid(2));
let pos = Vector2::new(position.x - rem.x, position.y - rem.y);
TransformSetCell(pos, OrthoTransformation(cell_position_to_transform(rem)))
}
pub fn with_transformation(self, trans: OrthoTransformation) -> Self {
TransformSetCell(self.0, trans)
}
}
impl OrthoTransform for TransformSetCell {
fn x_flipped(self) -> Self {
Self(self.0, self.1.x_flipped())
}
fn rotated(self, amount: i8) -> Self {
Self(self.0, self.1.rotated(amount))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::algebra::Point2;
use OrthoTransformation as Trans;
use TransformSetCell as Cell;
#[test]
fn identity() {
assert_eq!(Trans::identity(), Trans::new(false, 0));
for v in [
Vector2::new(2, 1),
Vector2::new(1, 2),
Vector2::new(-1, 5),
Vector2::new(-2, -2),
] {
assert_eq!(v.transformed(Trans::identity()), v);
}
}
fn matrix_check(trans: OrthoTransformation) {
let v = Vector2::new(1.0, 0.5);
let m = trans.matrix().to_homogeneous();
let p = m.transform_point(&Point2::from(v)).coords;
assert_eq!(p, v.transformed(trans), "{trans}");
}
#[test]
fn matrix() {
for i in 0..4 {
matrix_check(Trans::new(false, i))
}
for i in 0..4 {
matrix_check(Trans::new(true, i))
}
}
#[test]
fn rotate_4() {
assert_eq!(Trans::identity(), Trans::new(false, 4))
}
#[test]
fn invert() {
for i in [-4, -3, -2, -1, 1, 2, 3, 4] {
assert_eq!(
Trans(i).transformed(Trans(i).inverted()),
Trans::identity(),
"{:?}: {:?} {:?}",
i,
Trans(i),
Trans(i).inverted()
);
}
}
#[test]
fn inverse_undoes_transform() {
for i in [-4, -3, -2, -1, 1, 2, 3, 4] {
assert_eq!(
Vector2::new(2, 3)
.transformed(Trans(i))
.transformed(Trans(i).inverted()),
Vector2::new(2, 3),
);
}
}
#[test]
fn rotate_trans() {
assert_eq!(Trans::new(false, 0).rotated(0), Trans::new(false, 0));
assert_eq!(Trans::new(true, 0).rotated(0), Trans::new(true, 0));
assert_eq!(Trans::new(true, 2).rotated(0), Trans::new(true, 2));
assert_eq!(Trans::new(false, 0).rotated(1), Trans::new(false, 1));
assert_eq!(Trans::new(true, 0).rotated(1), Trans::new(true, 1));
assert_eq!(Trans::new(true, 2).rotated(1), Trans::new(true, 3));
assert_eq!(Trans::new(false, 0).rotated(2), Trans::new(false, 2));
assert_eq!(Trans::new(true, 0).rotated(2), Trans::new(true, 2));
assert_eq!(Trans::new(true, 2).rotated(2), Trans::new(true, 0));
}
#[test]
fn flipped_trans() {
assert_eq!(Trans::new(false, 0).x_flipped(), Trans::new(true, 0));
assert_eq!(Trans::new(true, 0).x_flipped(), Trans::new(false, 0));
assert_eq!(Trans::new(true, 2).x_flipped(), Trans::new(false, 2));
assert_eq!(Trans::new(true, 1).x_flipped(), Trans::new(false, 3));
assert_eq!(Trans::new(false, 0).y_flipped(), Trans::new(true, 2));
assert_eq!(Trans::new(true, 0).y_flipped(), Trans::new(false, 2));
assert_eq!(Trans::new(true, 2).y_flipped(), Trans::new(false, 0));
assert_eq!(Trans::new(true, 1).y_flipped(), Trans::new(false, 1));
}
#[test]
fn rotate_vector() {
assert_eq!(Vector2::new(1, 0).rotated(0), Vector2::new(1, 0));
assert_eq!(Vector2::new(0, 1).rotated(0), Vector2::new(0, 1));
assert_eq!(Vector2::new(2, 3).rotated(0), Vector2::new(2, 3));
assert_eq!(Vector2::new(1, 0).rotated(1), Vector2::new(0, 1));
assert_eq!(Vector2::new(0, 1).rotated(1), Vector2::new(-1, 0));
assert_eq!(Vector2::new(2, 3).rotated(1), Vector2::new(-3, 2));
assert_eq!(Vector2::new(1, 0).rotated(2), Vector2::new(-1, 0));
assert_eq!(Vector2::new(0, 1).rotated(2), Vector2::new(0, -1));
assert_eq!(Vector2::new(2, 3).rotated(2), Vector2::new(-2, -3));
assert_eq!(Vector2::new(1, 0).rotated(3), Vector2::new(0, -1));
assert_eq!(Vector2::new(0, 1).rotated(3), Vector2::new(1, 0));
assert_eq!(Vector2::new(2, 3).rotated(3), Vector2::new(3, -2));
assert_eq!(Vector2::new(1, 0).rotated(4), Vector2::new(1, 0));
assert_eq!(Vector2::new(0, 1).rotated(4), Vector2::new(0, 1));
assert_eq!(Vector2::new(2, 3).rotated(4), Vector2::new(2, 3));
}
#[test]
fn flipped_vector() {
assert_eq!(Vector2::new(1, 0).x_flipped(), Vector2::new(-1, 0));
assert_eq!(Vector2::new(0, 1).x_flipped(), Vector2::new(0, 1));
assert_eq!(Vector2::new(2, 3).x_flipped(), Vector2::new(-2, 3));
assert_eq!(Vector2::new(1, 0).y_flipped(), Vector2::new(1, 0));
assert_eq!(Vector2::new(0, 1).y_flipped(), Vector2::new(0, -1));
assert_eq!(Vector2::new(2, 3).y_flipped(), Vector2::new(2, -3));
}
#[test]
fn flipped() {
assert!(!Trans::new(false, -3).is_flipped());
assert!(!Trans::new(false, -2).is_flipped());
assert!(!Trans::new(false, -1).is_flipped());
assert!(!Trans::new(false, 0).is_flipped());
assert!(!Trans::new(false, 1).is_flipped());
assert!(!Trans::new(false, 2).is_flipped());
assert!(!Trans::new(false, 3).is_flipped());
assert!(!Trans::new(false, 4).is_flipped());
assert!(!Trans::new(false, 5).is_flipped());
assert!(Trans::new(true, -3).is_flipped());
assert!(Trans::new(true, -2).is_flipped());
assert!(Trans::new(true, -1).is_flipped());
assert!(Trans::new(true, 0).is_flipped());
assert!(Trans::new(true, 1).is_flipped());
assert!(Trans::new(true, 2).is_flipped());
assert!(Trans::new(true, 3).is_flipped());
assert!(Trans::new(true, 4).is_flipped());
assert!(Trans::new(true, 5).is_flipped());
}
#[test]
fn rotate_amount() {
assert_eq!(Trans::new(false, -3).rotation(), 1);
assert_eq!(Trans::new(false, -2).rotation(), 2);
assert_eq!(Trans::new(false, -1).rotation(), 3);
assert_eq!(Trans::new(false, 0).rotation(), 0);
assert_eq!(Trans::new(false, 1).rotation(), 1);
assert_eq!(Trans::new(false, 2).rotation(), 2);
assert_eq!(Trans::new(false, 3).rotation(), 3);
assert_eq!(Trans::new(false, 4).rotation(), 0);
assert_eq!(Trans::new(false, 5).rotation(), 1);
}
#[test]
fn flipped_rotate_amount() {
assert_eq!(Trans::new(true, -3).rotation(), 1);
assert_eq!(Trans::new(true, -2).rotation(), 2);
assert_eq!(Trans::new(true, -1).rotation(), 3);
assert_eq!(Trans::new(true, 0).rotation(), 0);
assert_eq!(Trans::new(true, 1).rotation(), 1);
assert_eq!(Trans::new(true, 2).rotation(), 2);
assert_eq!(Trans::new(true, 3).rotation(), 3);
assert_eq!(Trans::new(true, 4).rotation(), 0);
assert_eq!(Trans::new(true, 5).rotation(), 1);
}
#[test]
fn double_x_flip() {
assert_eq!(Trans::identity(), Trans::identity().x_flipped().x_flipped())
}
#[test]
fn double_y_flip() {
assert_eq!(Trans::identity(), Trans::identity().y_flipped().y_flipped())
}
#[test]
fn cell_from_position_0() {
assert_eq!(
Cell::from_position(Vector2::new(0, 0)),
Cell(Vector2::new(0, 0), Trans::identity())
);
}
#[test]
fn cell_from_position_1() {
assert_eq!(
Cell::from_position(Vector2::new(1, 0)),
Cell(Vector2::new(0, 0), Trans::identity().rotated(1))
);
}
#[test]
fn cell_from_position_2() {
assert_eq!(
Cell::from_position(Vector2::new(2, 0)),
Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(3))
);
}
#[test]
fn cell_from_position_negative_0() {
assert_eq!(
Cell::from_position(Vector2::new(0, -2)),
Cell(Vector2::new(0, -2), Trans::identity())
);
}
#[test]
fn cell_into_position_0() {
assert_eq!(
Cell(Vector2::new(0, 0), Trans::identity().rotated(0)).into_position(),
Vector2::new(0, 0),
);
assert_eq!(
Cell(Vector2::new(0, 0), Trans::identity().rotated(1)).into_position(),
Vector2::new(1, 0),
);
assert_eq!(
Cell(Vector2::new(0, 0), Trans::identity().rotated(2)).into_position(),
Vector2::new(1, 1),
);
assert_eq!(
Cell(Vector2::new(0, 0), Trans::identity().rotated(3)).into_position(),
Vector2::new(0, 1),
);
}
#[test]
fn cell_into_position_1() {
assert_eq!(
Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(0)).into_position(),
Vector2::new(3, 0),
);
assert_eq!(
Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(1)).into_position(),
Vector2::new(3, 1),
);
assert_eq!(
Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(2)).into_position(),
Vector2::new(2, 1),
);
assert_eq!(
Cell(Vector2::new(0, 0), Trans::identity().x_flipped().rotated(3)).into_position(),
Vector2::new(2, 0),
);
}
#[test]
fn cell_into_position_negative_0() {
assert_eq!(
Cell(Vector2::new(0, -2), Trans::identity().rotated(0)).into_position(),
Vector2::new(0, -2),
);
assert_eq!(
Cell(Vector2::new(0, -2), Trans::identity().rotated(1)).into_position(),
Vector2::new(1, -2),
);
assert_eq!(
Cell(Vector2::new(0, -2), Trans::identity().rotated(2)).into_position(),
Vector2::new(1, -1),
);
assert_eq!(
Cell(Vector2::new(0, -2), Trans::identity().rotated(3)).into_position(),
Vector2::new(0, -1),
);
}
#[test]
fn cell_uniqueness() {
let mut set = fxhash::FxHashSet::<Vector2<i32>>::default();
for t in Trans::all() {
set.insert(Cell(Vector2::new(0, 0), t).into_position());
}
assert_eq!(set.len(), 8, "{set:?}");
}
#[test]
fn cell_correctness() {
for x in 0..4 {
for y in 0..2 {
assert_eq!(
Cell::from_position(Vector2::new(x, y)).0,
Vector2::new(0, 0),
"{x}, {y}"
);
}
}
for x in 4..8 {
for y in -2..0 {
assert_eq!(
Cell::from_position(Vector2::new(x, y)).0,
Vector2::new(4, -2),
"{x}, {y}"
);
}
}
}
#[test]
fn cell_from_position_and_back() {
for x in -3..4 {
for y in -2..4 {
let p = Vector2::new(x, y);
assert_eq!(
Cell::from_position(p).into_position(),
p,
"Cell: {:?}",
Cell::from_position(p)
);
}
}
}
}