use core::{fmt::Display, ops};
use crate::{
Size,
int::{Int, SignedInt},
internal,
};
#[macro_export]
macro_rules! pos {
($x:expr, $y:expr) => {
Pos::new($x, $y)
};
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(C)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Pos<T = i32> {
pub x: T,
pub y: T,
}
#[allow(private_bounds)]
impl<T: Int> Pos<T> {
pub const ORIGIN: Self = Self {
x: T::ZERO,
y: T::ZERO,
};
pub const MIN: Self = Self {
x: T::MIN,
y: T::MIN,
};
pub const MAX: Self = Self {
x: T::MAX,
y: T::MAX,
};
pub const X: Self = Self {
x: T::ONE,
y: T::ZERO,
};
pub const Y: Self = Self {
x: T::ZERO,
y: T::ONE,
};
#[must_use]
pub const fn new(x: T, y: T) -> Self {
Self { x, y }
}
#[must_use]
pub fn normalized_approx(&self) -> Self {
if self == &Self::ORIGIN {
Self::ORIGIN
} else {
let gcd = internal::gcd(self.x, self.y);
Self {
x: self.x / gcd,
y: self.y / gcd,
}
}
}
#[must_use]
pub fn cmp_row_major(&self, other: &Self) -> core::cmp::Ordering {
self.cmp(other)
}
#[must_use]
pub fn cmp_lexicographic(&self, other: &Self) -> core::cmp::Ordering {
self.x.cmp(&other.x).then(self.y.cmp(&other.y))
}
}
impl<T: SignedInt> Pos<T> {
pub const NEG_X: Self = Self {
x: T::NEG_ONE,
y: T::ZERO,
};
pub const NEG_Y: Self = Self {
x: T::ZERO,
y: T::NEG_ONE,
};
}
impl<T: Int> Display for Pos<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
impl<T: Int> PartialOrd for Pos<T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T: Int> Ord for Pos<T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.y.cmp(&other.y).then(self.x.cmp(&other.x))
}
}
impl<T: Int> Default for Pos<T> {
fn default() -> Self {
Self::ORIGIN
}
}
impl<T: SignedInt> ops::Neg for Pos<T> {
type Output = Self;
fn neg(self) -> Self::Output {
Self {
x: -self.x,
y: -self.y,
}
}
}
impl<T: Int> ops::Add<Self> for Pos<T> {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl<T: Int> ops::AddAssign<Self> for Pos<T> {
fn add_assign(&mut self, rhs: Self) {
self.x += rhs.x;
self.y += rhs.y;
}
}
impl<T: Int> ops::Sub<Self> for Pos<T> {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
impl<T: Int> ops::SubAssign<Self> for Pos<T> {
fn sub_assign(&mut self, rhs: Self) {
self.x -= rhs.x;
self.y -= rhs.y;
}
}
impl<T: Int> ops::Mul<T> for Pos<T> {
type Output = Self;
fn mul(self, rhs: T) -> Self::Output {
Self {
x: self.x * rhs,
y: self.y * rhs,
}
}
}
impl<T: Int> ops::MulAssign<T> for Pos<T> {
fn mul_assign(&mut self, rhs: T) {
self.x *= rhs;
self.y *= rhs;
}
}
impl<T: Int> ops::Mul<Self> for Pos<T> {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self {
x: self.x * rhs.x,
y: self.y * rhs.y,
}
}
}
impl<T: Int> ops::MulAssign<Self> for Pos<T> {
fn mul_assign(&mut self, rhs: Self) {
self.x *= rhs.x;
self.y *= rhs.y;
}
}
impl<T: Int> ops::Div<T> for Pos<T> {
type Output = Self;
fn div(self, rhs: T) -> Self::Output {
Self {
x: self.x / rhs,
y: self.y / rhs,
}
}
}
impl<T: Int> ops::DivAssign<T> for Pos<T> {
fn div_assign(&mut self, rhs: T) {
self.x /= rhs;
self.y /= rhs;
}
}
impl<T: Int> ops::Div<Self> for Pos<T> {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
Self {
x: self.x / rhs.x,
y: self.y / rhs.y,
}
}
}
impl<T: Int> ops::DivAssign<Self> for Pos<T> {
fn div_assign(&mut self, rhs: Self) {
self.x /= rhs.x;
self.y /= rhs.y;
}
}
impl<T: Int> From<(T, T)> for Pos<T> {
fn from(value: (T, T)) -> Self {
Self::new(value.0, value.1)
}
}
impl<T: Int> From<Pos<T>> for (T, T) {
fn from(pos: Pos<T>) -> Self {
(pos.x, pos.y)
}
}
impl<T: Int> From<[T; 2]> for Pos<T> {
fn from(value: [T; 2]) -> Self {
Self::new(value[0], value[1])
}
}
impl<T: Int> From<Pos<T>> for [T; 2] {
fn from(pos: Pos<T>) -> Self {
[pos.x, pos.y]
}
}
pub trait TryFromPos<T: Int>: Sized {
fn try_from_pos(value: Pos<T>) -> Result<Self, TryFromPosError>;
}
pub trait TryIntoPos<T: Int>: Sized {
fn try_into_pos(self) -> Result<Pos<T>, TryFromPosError>;
}
impl<T, U> TryIntoPos<U> for Pos<T>
where
Pos<U>: TryFromPos<T>,
U: Int,
T: Int,
{
fn try_into_pos(self) -> Result<Pos<U>, TryFromPosError> {
Pos::<U>::try_from_pos(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TryFromPosError {
OutOfRange,
}
impl<S: Int, T: Int + TryFrom<S>> TryFromPos<S> for Pos<T> {
fn try_from_pos(value: Pos<S>) -> Result<Self, TryFromPosError> {
let x = T::try_from(value.x).map_err(|_| TryFromPosError::OutOfRange)?;
let y = T::try_from(value.y).map_err(|_| TryFromPosError::OutOfRange)?;
Ok(Self::new(x, y))
}
}
impl<T: Int> TryFrom<Pos<T>> for Size {
type Error = TryFromPosError;
fn try_from(value: Pos<T>) -> Result<Self, TryFromPosError> {
let width = value
.x
.checked_to_usize()
.ok_or(TryFromPosError::OutOfRange)?;
let height = value
.y
.checked_to_usize()
.ok_or(TryFromPosError::OutOfRange)?;
Ok(Self::new(width, height))
}
}
pub type Pos16 = Pos<u16>;
pub type PosI = Pos<i32>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn layout_is_c_struct() {
use core::mem::{offset_of, size_of};
#[repr(C)]
struct CPos {
x: i32,
y: i32,
}
assert_eq!(size_of::<Pos<i32>>(), size_of::<CPos>());
assert_eq!(offset_of!(Pos<i32>, x), offset_of!(CPos, x));
assert_eq!(offset_of!(Pos<i32>, y), offset_of!(CPos, y));
}
#[test]
fn pos_macro() {
const P: Pos<i32> = pos!(3, 4);
assert_eq!(P.x, 3);
assert_eq!(P.y, 4);
}
#[test]
fn ord_row_major() {
assert!(Pos::new(1, 2) < Pos::new(1, 3)); assert!(Pos::new(1, 2) < Pos::new(2, 2)); assert!(Pos::new(0, 3) > Pos::new(1, 2)); assert!(Pos::new(2, 1) < Pos::new(1, 2)); }
#[test]
fn cmp_row_major_y_primary() {
assert_eq!(
Pos::new(5, 2).cmp_row_major(&Pos::new(5, 3)),
core::cmp::Ordering::Less
);
}
#[test]
fn cmp_row_major_x_secondary() {
assert_eq!(
Pos::new(1, 3).cmp_row_major(&Pos::new(2, 3)),
core::cmp::Ordering::Less
);
}
#[test]
fn cmp_row_major_matches_ord() {
let a = Pos::new(1, 2);
let b = Pos::new(0, 3);
assert_eq!(a.cmp_row_major(&b), a.cmp(&b));
}
#[test]
fn cmp_lexicographic_x_primary() {
assert_eq!(
Pos::new(2, 5).cmp_lexicographic(&Pos::new(1, 5)),
core::cmp::Ordering::Greater
);
}
#[test]
fn cmp_lexicographic_y_secondary() {
assert_eq!(
Pos::new(3, 1).cmp_lexicographic(&Pos::new(3, 2)),
core::cmp::Ordering::Less
);
}
#[test]
fn cmp_lexicographic_differs_from_ord() {
let a = Pos::new(1, 2);
let b = Pos::new(0, 3);
assert_eq!(a.cmp_lexicographic(&b), core::cmp::Ordering::Greater); assert_eq!(a.cmp(&b), core::cmp::Ordering::Less); }
#[test]
fn cmp_row_major_equal() {
assert_eq!(
Pos::new(4, 5).cmp_row_major(&Pos::new(4, 5)),
core::cmp::Ordering::Equal
);
}
#[test]
fn generic_t_defaults_to_i32() {
let p: Pos = Pos::default();
assert_eq!(p, Pos::<i32>::ORIGIN);
}
#[test]
fn origin_is_0_0() {
assert_eq!(Pos::ORIGIN, Pos::new(0, 0));
}
#[test]
fn min_is_min_min() {
assert_eq!(Pos::MIN, Pos::new(i32::MIN, i32::MIN));
}
#[test]
fn max_is_max_max() {
assert_eq!(Pos::MAX, Pos::new(i32::MAX, i32::MAX));
}
#[test]
fn x_is_1_0() {
assert_eq!(Pos::X, Pos::new(1, 0));
}
#[test]
fn y_is_0_1() {
assert_eq!(Pos::Y, Pos::new(0, 1));
}
#[test]
fn new_x_y() {
let p = Pos::new(3, 4);
assert_eq!(p.x, 3);
assert_eq!(p.y, 4);
}
#[test]
fn default_is_origin() {
let p: Pos<i32> = Pos::default();
assert_eq!(p, Pos::ORIGIN);
}
#[test]
fn negate() {
let p = Pos::new(3, 4);
assert_eq!(-p, Pos::new(-3, -4));
}
#[test]
fn mul_by_scalar() {
let p = Pos::new(3, 4) * 2;
assert_eq!(p, Pos::new(6, 8));
}
#[test]
fn mul_assign_by_scalar() {
let mut p = Pos::new(3, 4);
p *= 2;
assert_eq!(p, Pos::new(6, 8));
}
#[test]
fn from_tuple() {
let pos = Pos::from((3, 4));
assert_eq!(pos.x, 3);
assert_eq!(pos.y, 4);
}
#[test]
fn from_array() {
let pos = Pos::from([3, 4]);
assert_eq!(pos.x, 3);
assert_eq!(pos.y, 4);
}
#[test]
fn into_tuple() {
let pos = Pos::new(3, 4);
let tuple: (i32, i32) = pos.into();
assert_eq!(tuple, (3, 4));
}
#[test]
fn into_array() {
let pos = Pos::new(3, 4);
let array: [i32; 2] = pos.into();
assert_eq!(array, [3, 4]);
}
#[test]
fn try_from_pos_ok() {
let source: Pos<u8> = Pos::new(3, 4);
let convert = Pos::<i32>::try_from_pos(source).unwrap();
assert_eq!(convert.x, 3);
assert_eq!(convert.y, 4);
}
#[test]
fn try_from_pos_out_of_range() {
let source: Pos<u16> = Pos::new(7000, 8000);
let result = Pos::<u8>::try_from_pos(source);
assert!(result.is_err());
}
#[test]
fn try_into_pos_ok() {
let source: Pos<u8> = Pos::new(3, 4);
let convert: Pos<i32> = source.try_into_pos().unwrap();
assert_eq!(convert.x, 3);
assert_eq!(convert.y, 4);
}
#[test]
fn try_into_pos_out_of_range() {
let source: Pos<u16> = Pos::new(7000, 8000);
let result: Result<Pos<u8>, TryFromPosError> = source.try_into_pos();
assert!(result.is_err());
}
#[test]
fn add_pos() {
let p1 = Pos::new(3, 4);
let p2 = Pos::new(1, 2);
assert_eq!(p1 + p2, Pos::new(4, 6));
}
#[test]
fn add_assign_pos() {
let mut p1 = Pos::new(3, 4);
let p2 = Pos::new(1, 2);
p1 += p2;
assert_eq!(p1, Pos::new(4, 6));
}
#[test]
fn sub_pos() {
let p1 = Pos::new(3, 4);
let p2 = Pos::new(1, 2);
assert_eq!(p1 - p2, Pos::new(2, 2));
}
#[test]
fn sub_assign_pos() {
let mut p1 = Pos::new(3, 4);
let p2 = Pos::new(1, 2);
p1 -= p2;
assert_eq!(p1, Pos::new(2, 2));
}
#[test]
fn into_size() {
let pos = Pos::new(3, 4);
let size = Size::try_from(pos).unwrap();
assert_eq!(size.width, 3);
assert_eq!(size.height, 4);
}
#[test]
fn into_size_wrapped() {
let pos = Pos::new(-3, -4);
let size = Size::try_from(pos);
assert!(size.is_err());
}
#[test]
fn normalized() {
assert_eq!(Pos::new(0, 0).normalized_approx(), Pos::ORIGIN);
assert_eq!(Pos::new(1, 0).normalized_approx(), Pos::X);
assert_eq!(Pos::new(0, 1).normalized_approx(), Pos::Y);
assert_eq!(Pos::new(2, 0).normalized_approx(), Pos::X);
assert_eq!(Pos::new(0, 2).normalized_approx(), Pos::Y);
assert_eq!(
Pos::new(6, 8).normalized_approx(),
Pos::new(3, 4).normalized_approx()
);
}
#[test]
fn mul_scalar() {
let p = Pos::new(3, 4) * 2;
assert_eq!(p, Pos::new(6, 8));
}
#[test]
fn mul_assign_scalar() {
let mut p = Pos::new(3, 4);
p *= 2;
assert_eq!(p, Pos::new(6, 8));
}
#[test]
fn mul_pos() {
let p1 = Pos::new(3, 4);
let p2 = Pos::new(2, 3);
assert_eq!(p1 * p2, Pos::new(6, 12));
}
#[test]
fn mul_assign_pos() {
let mut p1 = Pos::new(3, 4);
let p2 = Pos::new(2, 3);
p1 *= p2;
assert_eq!(p1, Pos::new(6, 12));
}
#[test]
fn div_scalar() {
let p = Pos::new(6, 8) / 2;
assert_eq!(p, Pos::new(3, 4));
}
#[test]
fn div_assign_scalar() {
let mut p = Pos::new(6, 8);
p /= 2;
assert_eq!(p, Pos::new(3, 4));
}
#[test]
fn div_pos() {
let p1 = Pos::new(6, 8);
let p2 = Pos::new(2, 4);
assert_eq!(p1 / p2, Pos::new(3, 2));
}
#[test]
fn div_assign_pos() {
let mut p1 = Pos::new(6, 8);
let p2 = Pos::new(2, 4);
p1 /= p2;
assert_eq!(p1, Pos::new(3, 2));
}
#[test]
fn pos16_alias() {
let p: Pos16 = Pos16::new(1, 2);
assert_eq!(p.x, 1u16);
assert_eq!(p.y, 2u16);
}
#[test]
fn posi_alias() {
let p: PosI = PosI::new(1, 2);
assert_eq!(p.x, 1i32);
assert_eq!(p.y, 2i32);
}
}