use core::fmt::Display;
use bevy_math::IVec2;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[derive(Debug, Clone, Copy, Reflect, Default, PartialEq, Eq, Hash)]
#[reflect(Default, Clone, PartialEq, Hash)]
#[repr(u8)]
pub enum TileOrientation {
#[default]
Default = 0b000,
Rotate90 = 0b011,
Rotate180 = 0b110,
Rotate270 = 0b101,
MirrorH = 0b100,
MirrorHRotate90 = 0b001,
MirrorHRotate180 = 0b010,
MirrorHRotate270 = 0b111,
}
impl TileOrientation {
const MIRROR_H_BIT: u8 = 0b100;
const MIRROR_V_BIT: u8 = 0b010;
const MIRROR_D_BIT: u8 = 0b001;
pub fn from_bools(mirror_h: bool, mirror_v: bool, mirror_d: bool) -> TileOrientation {
match (mirror_h, mirror_v, mirror_d) {
(false, false, false) => TileOrientation::Default,
(false, true, true) => TileOrientation::Rotate90,
(true, true, false) => TileOrientation::Rotate180,
(true, false, true) => TileOrientation::Rotate270,
(true, false, false) => TileOrientation::MirrorH,
(false, false, true) => TileOrientation::MirrorHRotate90,
(false, true, false) => TileOrientation::MirrorHRotate180,
(true, true, true) => TileOrientation::MirrorHRotate270,
}
}
pub fn mirror_h(&self) -> bool {
(*self as u8) & Self::MIRROR_H_BIT != 0
}
pub fn mirror_v(&self) -> bool {
(*self as u8) & Self::MIRROR_V_BIT != 0
}
pub fn mirror_d(&self) -> bool {
(*self as u8) & Self::MIRROR_D_BIT != 0
}
pub fn inverse(&self) -> TileOrientation {
match self {
Self::Default => Self::Default,
Self::Rotate90 => Self::Rotate270,
Self::Rotate180 => Self::Rotate180,
Self::Rotate270 => Self::Rotate90,
Self::MirrorH => Self::MirrorH,
Self::MirrorHRotate90 => Self::MirrorHRotate90,
Self::MirrorHRotate180 => Self::MirrorHRotate180,
Self::MirrorHRotate270 => Self::MirrorHRotate270,
}
}
pub fn apply_to_ivec2(&self, pos: &IVec2) -> IVec2 {
let mut x = pos.x;
let mut y = pos.y;
y = -y;
if self.mirror_d() {
(x, y) = (y, x);
}
if self.mirror_h() {
x = -x;
}
if self.mirror_v() {
y = -y;
}
y = -y;
IVec2::new(x, y)
}
pub fn and_then(&self, then: TileOrientation) -> TileOrientation {
let mut mirror_h = self.mirror_h();
let mut mirror_v = self.mirror_v();
let mut mirror_d = self.mirror_d();
if then.mirror_d() {
mirror_d = !mirror_d;
(mirror_h, mirror_v) = (mirror_v, mirror_h);
}
if then.mirror_h() {
mirror_h = !mirror_h;
}
if then.mirror_v() {
mirror_v = !mirror_v;
}
Self::from_bools(mirror_h, mirror_v, mirror_d)
}
}
impl Display for TileOrientation {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Default => write!(f, "Default"),
Self::Rotate90 => write!(f, "Rotate90"),
Self::Rotate180 => write!(f, "Rotate180"),
Self::Rotate270 => write!(f, "Rotate270"),
Self::MirrorH => write!(f, "MirrorX"),
Self::MirrorHRotate90 => write!(f, "MirrorXRotate90"),
Self::MirrorHRotate180 => write!(f, "MirrorXRotate180"),
Self::MirrorHRotate270 => write!(f, "MirrorXRotate270"),
}
}
}
#[cfg(test)]
mod tests {
use bevy_math::ivec2;
use super::*;
const CASES: [(TileOrientation, bool, bool, bool); 8] = [
(TileOrientation::Default, false, false, false),
(TileOrientation::Rotate90, false, true, true),
(TileOrientation::Rotate180, true, true, false),
(TileOrientation::Rotate270, true, false, true),
(TileOrientation::MirrorH, true, false, false),
(TileOrientation::MirrorHRotate90, false, false, true),
(TileOrientation::MirrorHRotate180, false, true, false),
(TileOrientation::MirrorHRotate270, true, true, true),
];
#[test]
fn mirror_and_swap_bits_should_extract_correctly() {
for (orientation, mirror_h, mirror_v, mirror_d) in CASES.iter() {
assert_eq!(orientation.mirror_h(), *mirror_h);
assert_eq!(orientation.mirror_v(), *mirror_v);
assert_eq!(orientation.mirror_d(), *mirror_d);
}
}
#[test]
fn from_bools_should_give_correct_orientation() {
for (orientation, mirror_h, mirror_v, mirror_d) in CASES.iter() {
let from_bools = TileOrientation::from_bools(*mirror_h, *mirror_v, *mirror_d);
assert_eq!(*orientation, from_bools);
let from_bools_u8 = from_bools as u8;
assert_eq!(
from_bools_u8 & TileOrientation::MIRROR_H_BIT != 0,
*mirror_h
);
assert_eq!(
from_bools_u8 & TileOrientation::MIRROR_V_BIT != 0,
*mirror_v
);
assert_eq!(
from_bools_u8 & TileOrientation::MIRROR_D_BIT != 0,
*mirror_d
);
}
}
#[test]
fn applying_transform_and_then_inverse_transform_should_yield_default() {
for (orientation, ..) in CASES.iter() {
let inverse = orientation.inverse();
let transform_then_inverse = orientation.and_then(inverse);
assert_eq!(transform_then_inverse, TileOrientation::Default);
}
}
const FROM_POS: IVec2 = ivec2(1, 2);
const POS_CASES: [(TileOrientation, IVec2); 8] = [
(TileOrientation::Default, ivec2(1, 2)),
(TileOrientation::Rotate90, ivec2(-2, 1)),
(TileOrientation::Rotate180, ivec2(-1, -2)),
(TileOrientation::Rotate270, ivec2(2, -1)),
(TileOrientation::MirrorH, ivec2(-1, 2)),
(TileOrientation::MirrorHRotate90, ivec2(-2, -1)),
(TileOrientation::MirrorHRotate180, ivec2(1, -2)),
(TileOrientation::MirrorHRotate270, ivec2(2, 1)),
];
#[test]
fn applying_to_pos_should_give_correct_new_pos() {
for (orientation, end_pos) in POS_CASES.iter() {
let transform_end_pos = orientation.apply_to_ivec2(&FROM_POS);
assert_eq!(
end_pos, &transform_end_pos,
"{:?} should map {} to {}, but we got {}",
orientation, FROM_POS, end_pos, transform_end_pos
);
}
}
#[test]
fn applying_transform_then_inverse_should_leave_pos_unaltered() {
for (orientation, ..) in CASES.iter() {
let transformed = orientation.apply_to_ivec2(&FROM_POS);
let transformed_back = orientation.inverse().apply_to_ivec2(&transformed);
assert_eq!(FROM_POS, transformed_back);
}
}
#[test]
fn applying_inverse_then_transform_should_leave_pos_unaltered() {
for (orientation, ..) in CASES.iter() {
let inverse_transformed = orientation.inverse().apply_to_ivec2(&FROM_POS);
let transformed_back = orientation.apply_to_ivec2(&inverse_transformed);
assert_eq!(FROM_POS, transformed_back);
}
}
#[test]
fn applying_any_transform_pair_individually_to_a_pos_should_give_same_result_as_applying_combined_single_transform(
) {
for (first, ..) in CASES.iter() {
for (second, ..) in CASES.iter() {
let combined = first.and_then(*second);
assert_eq!(
second.apply_to_ivec2(&first.apply_to_ivec2(&FROM_POS)),
combined.apply_to_ivec2(&FROM_POS)
);
}
}
}
}