use core::ops::Neg;
use crate::Dir2;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Deserialize, Serialize)
)]
pub enum CompassQuadrant {
North,
East,
South,
West,
}
impl CompassQuadrant {
pub const fn from_index(index: usize) -> Option<Self> {
match index {
0 => Some(Self::North),
1 => Some(Self::East),
2 => Some(Self::South),
3 => Some(Self::West),
_ => None,
}
}
pub const fn to_index(self) -> usize {
match self {
Self::North => 0,
Self::East => 1,
Self::South => 2,
Self::West => 3,
}
}
pub const fn opposite(&self) -> CompassQuadrant {
match self {
Self::North => Self::South,
Self::East => Self::West,
Self::South => Self::North,
Self::West => Self::East,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Deserialize, Serialize)
)]
pub enum CompassOctant {
North,
NorthEast,
East,
SouthEast,
South,
SouthWest,
West,
NorthWest,
}
impl CompassOctant {
pub const fn from_index(index: usize) -> Option<Self> {
match index {
0 => Some(Self::North),
1 => Some(Self::NorthEast),
2 => Some(Self::East),
3 => Some(Self::SouthEast),
4 => Some(Self::South),
5 => Some(Self::SouthWest),
6 => Some(Self::West),
7 => Some(Self::NorthWest),
_ => None,
}
}
pub const fn to_index(self) -> usize {
match self {
Self::North => 0,
Self::NorthEast => 1,
Self::East => 2,
Self::SouthEast => 3,
Self::South => 4,
Self::SouthWest => 5,
Self::West => 6,
Self::NorthWest => 7,
}
}
pub const fn opposite(&self) -> CompassOctant {
match self {
Self::North => Self::South,
Self::NorthEast => Self::SouthWest,
Self::East => Self::West,
Self::SouthEast => Self::NorthWest,
Self::South => Self::North,
Self::SouthWest => Self::NorthEast,
Self::West => Self::East,
Self::NorthWest => Self::SouthEast,
}
}
}
impl From<CompassQuadrant> for Dir2 {
fn from(q: CompassQuadrant) -> Self {
match q {
CompassQuadrant::North => Dir2::NORTH,
CompassQuadrant::East => Dir2::EAST,
CompassQuadrant::South => Dir2::SOUTH,
CompassQuadrant::West => Dir2::WEST,
}
}
}
impl From<Dir2> for CompassQuadrant {
fn from(dir: Dir2) -> Self {
let angle = dir.to_angle().to_degrees();
match angle {
-135.0..=-45.0 => Self::South,
-45.0..=45.0 => Self::East,
45.0..=135.0 => Self::North,
135.0..=180.0 | -180.0..=-135.0 => Self::West,
_ => unreachable!(),
}
}
}
impl From<CompassOctant> for Dir2 {
fn from(o: CompassOctant) -> Self {
match o {
CompassOctant::North => Dir2::NORTH,
CompassOctant::NorthEast => Dir2::NORTH_EAST,
CompassOctant::East => Dir2::EAST,
CompassOctant::SouthEast => Dir2::SOUTH_EAST,
CompassOctant::South => Dir2::SOUTH,
CompassOctant::SouthWest => Dir2::SOUTH_WEST,
CompassOctant::West => Dir2::WEST,
CompassOctant::NorthWest => Dir2::NORTH_WEST,
}
}
}
impl From<Dir2> for CompassOctant {
fn from(dir: Dir2) -> Self {
let angle = dir.to_angle().to_degrees();
match angle {
-112.5..=-67.5 => Self::South,
-67.5..=-22.5 => Self::SouthEast,
-22.5..=22.5 => Self::East,
22.5..=67.5 => Self::NorthEast,
67.5..=112.5 => Self::North,
112.5..=157.5 => Self::NorthWest,
157.5..=180.0 | -180.0..=-157.5 => Self::West,
-157.5..=-112.5 => Self::SouthWest,
_ => unreachable!(),
}
}
}
impl Neg for CompassQuadrant {
type Output = CompassQuadrant;
fn neg(self) -> Self::Output {
self.opposite()
}
}
impl Neg for CompassOctant {
type Output = CompassOctant;
fn neg(self) -> Self::Output {
self.opposite()
}
}
#[cfg(test)]
mod test_compass_quadrant {
use crate::{CompassQuadrant, Dir2, Vec2};
#[test]
fn test_cardinal_directions() {
let tests = [
(
Dir2::new(Vec2::new(1.0, 0.0)).unwrap(),
CompassQuadrant::East,
),
(
Dir2::new(Vec2::new(0.0, 1.0)).unwrap(),
CompassQuadrant::North,
),
(
Dir2::new(Vec2::new(-1.0, 0.0)).unwrap(),
CompassQuadrant::West,
),
(
Dir2::new(Vec2::new(0.0, -1.0)).unwrap(),
CompassQuadrant::South,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn test_north_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.1, 0.9)).unwrap(),
CompassQuadrant::North,
),
(
Dir2::new(Vec2::new(0.1, 0.9)).unwrap(),
CompassQuadrant::North,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn test_east_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(0.9, 0.1)).unwrap(),
CompassQuadrant::East,
),
(
Dir2::new(Vec2::new(0.9, -0.1)).unwrap(),
CompassQuadrant::East,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn test_south_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.1, -0.9)).unwrap(),
CompassQuadrant::South,
),
(
Dir2::new(Vec2::new(0.1, -0.9)).unwrap(),
CompassQuadrant::South,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn test_west_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.9, -0.1)).unwrap(),
CompassQuadrant::West,
),
(
Dir2::new(Vec2::new(-0.9, 0.1)).unwrap(),
CompassQuadrant::West,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn out_of_bounds_indexes_return_none() {
assert_eq!(CompassQuadrant::from_index(4), None);
assert_eq!(CompassQuadrant::from_index(5), None);
assert_eq!(CompassQuadrant::from_index(usize::MAX), None);
}
#[test]
fn compass_indexes_are_reversible() {
for i in 0..4 {
let quadrant = CompassQuadrant::from_index(i).unwrap();
assert_eq!(quadrant.to_index(), i);
}
}
#[test]
fn opposite_directions_reverse_themselves() {
for i in 0..4 {
let quadrant = CompassQuadrant::from_index(i).unwrap();
assert_eq!(-(-quadrant), quadrant);
}
}
}
#[cfg(test)]
mod test_compass_octant {
use crate::{CompassOctant, Dir2, Vec2};
#[test]
fn test_cardinal_directions() {
let tests = [
(
Dir2::new(Vec2::new(-0.5, 0.5)).unwrap(),
CompassOctant::NorthWest,
),
(
Dir2::new(Vec2::new(0.0, 1.0)).unwrap(),
CompassOctant::North,
),
(
Dir2::new(Vec2::new(0.5, 0.5)).unwrap(),
CompassOctant::NorthEast,
),
(Dir2::new(Vec2::new(1.0, 0.0)).unwrap(), CompassOctant::East),
(
Dir2::new(Vec2::new(0.5, -0.5)).unwrap(),
CompassOctant::SouthEast,
),
(
Dir2::new(Vec2::new(0.0, -1.0)).unwrap(),
CompassOctant::South,
),
(
Dir2::new(Vec2::new(-0.5, -0.5)).unwrap(),
CompassOctant::SouthWest,
),
(
Dir2::new(Vec2::new(-1.0, 0.0)).unwrap(),
CompassOctant::West,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_north_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.1, 0.9)).unwrap(),
CompassOctant::North,
),
(
Dir2::new(Vec2::new(0.1, 0.9)).unwrap(),
CompassOctant::North,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_north_east_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(0.4, 0.6)).unwrap(),
CompassOctant::NorthEast,
),
(
Dir2::new(Vec2::new(0.6, 0.4)).unwrap(),
CompassOctant::NorthEast,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_east_pie_slice() {
let tests = [
(Dir2::new(Vec2::new(0.9, 0.1)).unwrap(), CompassOctant::East),
(
Dir2::new(Vec2::new(0.9, -0.1)).unwrap(),
CompassOctant::East,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_south_east_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(0.4, -0.6)).unwrap(),
CompassOctant::SouthEast,
),
(
Dir2::new(Vec2::new(0.6, -0.4)).unwrap(),
CompassOctant::SouthEast,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_south_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.1, -0.9)).unwrap(),
CompassOctant::South,
),
(
Dir2::new(Vec2::new(0.1, -0.9)).unwrap(),
CompassOctant::South,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_south_west_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.4, -0.6)).unwrap(),
CompassOctant::SouthWest,
),
(
Dir2::new(Vec2::new(-0.6, -0.4)).unwrap(),
CompassOctant::SouthWest,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_west_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.9, -0.1)).unwrap(),
CompassOctant::West,
),
(
Dir2::new(Vec2::new(-0.9, 0.1)).unwrap(),
CompassOctant::West,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_north_west_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.4, 0.6)).unwrap(),
CompassOctant::NorthWest,
),
(
Dir2::new(Vec2::new(-0.6, 0.4)).unwrap(),
CompassOctant::NorthWest,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn out_of_bounds_indexes_return_none() {
assert_eq!(CompassOctant::from_index(8), None);
assert_eq!(CompassOctant::from_index(9), None);
assert_eq!(CompassOctant::from_index(usize::MAX), None);
}
#[test]
fn compass_indexes_are_reversible() {
for i in 0..8 {
let octant = CompassOctant::from_index(i).unwrap();
assert_eq!(octant.to_index(), i);
}
}
#[test]
fn opposite_directions_reverse_themselves() {
for i in 0..8 {
let octant = CompassOctant::from_index(i).unwrap();
assert_eq!(-(-octant), octant);
}
}
}