From 0db9fc92cde7c1ba72d5dfbc2f19041e550b2f11 Mon Sep 17 00:00:00 2001 From: Bob Gardner Date: Mon, 3 Jun 2024 12:46:50 -0700 Subject: [PATCH] Added CompassQuadrant and CompassOctant as per #13647 (#13653) # Objective Implements #13647 ## Solution Created two enums, CompassQuadrant and CompassOctant inside compass.rs with impls To and From Dir2. Used dir.to_angle().to_degrees() and matched against the resulting value. I could have skipped to_degrees() and matched against the radian value, but I thought this was more readable. I'm probably wrong lol. ## Testing Tested various dirs to compass variations. --- --------- Co-authored-by: Alice Cecile --- crates/bevy_math/src/compass.rs | 401 ++++++++++++++++++++++++++++++++ crates/bevy_math/src/lib.rs | 2 + 2 files changed, 403 insertions(+) create mode 100644 crates/bevy_math/src/compass.rs diff --git a/crates/bevy_math/src/compass.rs b/crates/bevy_math/src/compass.rs new file mode 100644 index 0000000000..2e40c55b5a --- /dev/null +++ b/crates/bevy_math/src/compass.rs @@ -0,0 +1,401 @@ +use crate::Dir2; +#[cfg(feature = "bevy_reflect")] +use bevy_reflect::Reflect; +#[cfg(all(feature = "serialize", feature = "bevy_reflect"))] +use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; + +/// A compass enum with 4 directions. +#[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))] +#[cfg_attr( + all(feature = "serialize", feature = "bevy_reflect"), + reflect(Deserialize, Serialize) +)] +pub enum CompassQuadrant { + /// Corresponds to [`Dir2::Y`] and [`Dir2::NORTH`] + North, + /// Corresponds to [`Dir2::X`] and [`Dir2::EAST`] + East, + /// Corresponds to [`Dir2::NEG_X`] and [`Dir2::SOUTH`] + South, + /// Corresponds to [`Dir2::NEG_Y`] and [`Dir2::WEST`] + West, +} + +/// A compass enum with 8 directions. +#[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))] +#[cfg_attr( + all(feature = "serialize", feature = "bevy_reflect"), + reflect(Deserialize, Serialize) +)] +pub enum CompassOctant { + /// Corresponds to [`Dir2::Y`] and [`Dir2::NORTH`] + North, + /// Corresponds to [`Dir2::NORTH_EAST`] + NorthEast, + /// Corresponds to [`Dir2::X`] and [`Dir2::EAST`] + East, + /// Corresponds to [`Dir2::SOUTH_EAST`] + SouthEast, + /// Corresponds to [`Dir2::NEG_X`] and [`Dir2::SOUTH`] + South, + /// Corresponds to [`Dir2::SOUTH_WEST`] + SouthWest, + /// Corresponds to [`Dir2::NEG_Y`] and [`Dir2::WEST`] + West, + /// Corresponds to [`Dir2::NORTH_WEST`] + NorthWest, +} + +impl From 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 for CompassQuadrant { + /// Converts a [`Dir2`] to a [`CompassQuadrant`] in a lossy manner. + /// Converting back to a [`Dir2`] is not guaranteed to yield the same value. + 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 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 for CompassOctant { + /// Converts a [`Dir2`] to a [`CompassOctant`] in a lossy manner. + /// Converting back to a [`Dir2`] is not guaranteed to yield the same value. + 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!(), + } + } +} + +#[cfg(test)] +mod test_compass_quadrant { + use crate::{CompassQuadrant, Dir2, Vec2}; + + #[test] + fn test_cardinal_directions() { + let tests = vec![ + ( + 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 = vec![ + ( + 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 = vec![ + ( + 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 = vec![ + ( + 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 = vec![ + ( + 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); + } + } +} + +#[cfg(test)] +mod test_compass_octant { + use crate::{CompassOctant, Dir2, Vec2}; + + #[test] + fn test_cardinal_directions() { + let tests = vec![ + ( + 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 = vec![ + ( + 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 = vec![ + ( + 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 = vec![ + (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 = vec![ + ( + 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 = vec![ + ( + 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 = vec![ + ( + 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 = vec![ + ( + 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 = vec![ + ( + 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); + } + } +} diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index c98c328d1b..dddb8be219 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -15,6 +15,7 @@ mod affine3; mod aspect_ratio; pub mod bounding; mod common_traits; +mod compass; pub mod cubic_splines; mod direction; mod float_ord; @@ -24,6 +25,7 @@ mod rects; mod rotation2d; #[cfg(feature = "rand")] pub mod sampling; +pub use compass::{CompassOctant, CompassQuadrant}; pub use affine3::*; pub use aspect_ratio::AspectRatio;