Files
another-boids-in-rust/vendor/bevy_math/src/compass.rs

586 lines
16 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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};
/// A compass enum with 4 directions.
/// ```text
/// N (North)
/// ▲
/// │
/// │
/// W (West) ┼─────► E (East)
/// │
/// │
/// ▼
/// S (South)
/// ```
#[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 {
/// 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,
}
impl CompassQuadrant {
/// Converts a standard index to a [`CompassQuadrant`].
///
/// Starts at 0 for [`CompassQuadrant::North`] and increments clockwise.
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,
}
}
/// Converts a [`CompassQuadrant`] to a standard index.
///
/// Starts at 0 for [`CompassQuadrant::North`] and increments clockwise.
pub const fn to_index(self) -> usize {
match self {
Self::North => 0,
Self::East => 1,
Self::South => 2,
Self::West => 3,
}
}
/// Returns the opposite [`CompassQuadrant`], located 180 degrees from `self`.
///
/// This can also be accessed via the `-` operator, using the [`Neg`] trait.
pub const fn opposite(&self) -> CompassQuadrant {
match self {
Self::North => Self::South,
Self::East => Self::West,
Self::South => Self::North,
Self::West => Self::East,
}
}
}
/// A compass enum with 8 directions.
/// ```text
/// N (North)
/// ▲
/// NW │ NE
/// ╲ │
/// W (West) ┼─────► E (East)
/// │ ╲
/// SW │ SE
/// ▼
/// S (South)
/// ```
#[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 {
/// 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 CompassOctant {
/// Converts a standard index to a [`CompassOctant`].
///
/// Starts at 0 for [`CompassOctant::North`] and increments clockwise.
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,
}
}
/// Converts a [`CompassOctant`] to a standard index.
///
/// Starts at 0 for [`CompassOctant::North`] and increments clockwise.
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,
}
}
/// Returns the opposite [`CompassOctant`], located 180 degrees from `self`.
///
/// This can also be accessed via the `-` operator, using the [`Neg`] trait.
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 {
/// 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<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 {
/// 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!(),
}
}
}
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);
}
}
}