use std::fmt;
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use num_traits::{real::Real, AsPrimitive, One, Signed, Zero};
use super::directions::Direction;
use super::line::Line;
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
#[repr(C)]
pub struct Point<T> {
x: T,
y: T,
z: T,
w: T,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct PointAxial<T> {
pub x: T,
pub y: T,
pub w: T,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
#[repr(C)]
pub struct Vector<T> {
x: T,
y: T,
z: T,
w: T,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct VectorAxial<T> {
pub x: T,
pub y: T,
pub w: T,
}
macro_rules! impl_hex_common {
($cube:ident, $axial:ident) => {
impl<T> $cube<T> {
pub fn x(&self) -> &T {
&self.x
}
pub fn y(&self) -> &T {
&self.y
}
pub fn z(&self) -> &T {
&self.z
}
pub fn w(&self) -> &T {
&self.w
}
pub fn into_axial(self) -> $axial<T> {
self.into()
}
}
impl<T: fmt::Display> fmt::Display for $cube<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "HexC({}, {}, {}, {})", self.x, self.y, self.z, self.w)
}
}
impl<T: Copy + Add + Zero> $cube<T> {
pub fn try_from_coords(x: T, y: T, z: T, w: T) -> Option<Self> {
if T::is_zero(&(x + y + z)) {
Some($cube { x, y, z, w })
} else {
None
}
}
pub fn new(x: T, y: T, z: T, w: T) -> Self {
Self::try_from_coords(x, y, z, w).expect("invalid cube coords")
}
}
impl<T: Zero> $cube<T> {
pub fn zero() -> Self {
$cube {
x: T::zero(),
y: T::zero(),
z: T::zero(),
w: T::zero(),
}
}
}
impl<T: Real> $cube<T> {
pub fn round(self) -> Self {
let x = self.x.round();
let y = self.y.round();
let z = self.z.round();
let w = self.w.round();
let x_diff = (x - self.x).abs();
let y_diff = (y - self.y).abs();
let z_diff = (z - self.z).abs();
if x_diff > y_diff && x_diff > z_diff {
$cube {
x: T::zero() - y - z,
y,
z,
w,
}
} else if y_diff > z_diff {
$cube {
x,
y: T::zero() - x - z,
z,
w,
}
} else {
$cube {
x,
y,
z: T::zero() - x - y,
w,
}
}
}
pub fn cast_round<N>(self) -> $cube<N>
where
N: 'static + Copy,
T: AsPrimitive<N>,
{
let rounded = self.round();
$cube {
x: rounded.x.as_(),
y: rounded.y.as_(),
z: rounded.z.as_(),
w: rounded.w.as_(),
}
}
}
impl<T> $cube<T> {
pub fn cast_fix<N>(self) -> $cube<N>
where
N: 'static + Copy + Add + Zero + Sub<Output = N>,
T: AsPrimitive<N>,
{
$axial::new(self.x.as_(), self.y.as_(), self.w.as_()).to_cube()
}
}
impl<T> $axial<T> {
pub fn new(x: T, y: T, w: T) -> Self {
$axial { x, y, w }
}
}
impl<T: fmt::Display> fmt::Display for $axial<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "HexA({}, {}, {})", self.x, self.y, self.w)
}
}
impl<T: Zero> $axial<T> {
pub fn zero() -> Self {
$axial {
x: T::zero(),
y: T::zero(),
w: T::zero(),
}
}
}
impl<T: One> $axial<T> {
pub fn one() -> Self {
$axial {
x: T::one(),
y: T::one(),
w: T::one(),
}
}
}
impl<T: Copy + Zero + Sub<Output = T>> $axial<T> {
pub fn to_cube(self) -> $cube<T> {
self.into()
}
}
impl<T: Real> $axial<T> {
pub fn round(self) -> Self {
self.to_cube().round().into_axial()
}
}
impl<T> $axial<T> {
pub fn cast<N>(self) -> $axial<N>
where
N: 'static + Copy,
T: AsPrimitive<N>,
{
$axial::new(self.x.as_(), self.y.as_(), self.w.as_())
}
}
impl<T> From<$cube<T>> for $axial<T> {
fn from(c: $cube<T>) -> Self {
$axial {
x: c.x,
y: c.y,
w: c.w,
}
}
}
impl<T: Copy + Zero + Sub<Output = T>> From<$axial<T>> for $cube<T> {
fn from(c: $axial<T>) -> $cube<T> {
$cube {
x: c.x,
y: c.y,
z: T::zero() - c.x - c.y,
w: c.w,
}
}
}
};
}
impl_hex_common!(Point, PointAxial);
impl_hex_common!(Vector, VectorAxial);
impl<T> Point<T> {
pub fn into_vector(self) -> Vector<T> {
Vector {
x: self.x,
y: self.y,
z: self.z,
w: self.w,
}
}
}
impl<T> PointAxial<T> {
pub fn into_vector(self) -> VectorAxial<T> {
VectorAxial {
x: self.x,
y: self.y,
w: self.w,
}
}
}
impl<T> Point<T>
where
T: 'static + Copy + AsPrimitive<f32>,
f32: AsPrimitive<T>,
{
pub fn line_to(self, other: Point<T>) -> Line<T> {
Line::new(self, other, false, false)
}
pub fn line_from(self, other: Point<T>) -> Line<T> {
Line::new(other, self, false, false)
}
pub fn line_to_excluding(
self,
other: Point<T>,
exclude_start: bool,
exclude_end: bool,
) -> Line<T> {
Line::new(self, other, exclude_start, exclude_end)
}
pub fn line_from_excluding(
self,
other: Point<T>,
exclude_start: bool,
exclude_end: bool,
) -> Line<T> {
Line::new(other, self, exclude_start, exclude_end)
}
}
impl<T: Copy + Signed + One + Add + Sub<Output = T> + Div> Point<T> {
pub fn distance(&self, other: Point<T>) -> T {
(other - *self).length()
}
}
impl<T> Vector<T> {
pub fn into_point(self) -> Point<T> {
Point {
x: self.x,
y: self.y,
z: self.z,
w: self.w,
}
}
}
impl<T> VectorAxial<T> {
pub fn into_point(self) -> PointAxial<T> {
PointAxial {
x: self.x,
y: self.y,
w: self.w,
}
}
}
impl<T: Zero + One + Neg<Output = T>> Vector<T> {
pub fn yz() -> Vector<T> {
Vector {
x: T::zero(),
y: T::one(),
z: -T::one(),
w: T::zero(),
}
}
pub fn xz() -> Vector<T> {
Vector {
x: T::one(),
y: T::zero(),
z: -T::one(),
w: T::zero(),
}
}
pub fn xy() -> Vector<T> {
Vector {
x: T::one(),
y: -T::one(),
z: T::zero(),
w: T::zero(),
}
}
pub fn zy() -> Vector<T> {
Vector {
x: T::zero(),
y: -T::one(),
z: T::one(),
w: T::zero(),
}
}
pub fn zx() -> Vector<T> {
Vector {
x: -T::one(),
y: T::zero(),
z: T::one(),
w: T::zero(),
}
}
pub fn yx() -> Vector<T> {
Vector {
x: -T::one(),
y: T::one(),
z: T::zero(),
w: T::zero(),
}
}
pub fn up() -> Vector<T> {
Vector {
x: T::zero(),
y: T::zero(),
z: T::zero(),
w: T::one(),
}
}
pub fn down() -> Vector<T> {
Vector {
x: T::zero(),
y: T::zero(),
z: T::zero(),
w: -T::one(),
}
}
pub fn directions() -> [Vector<T>; 8] {
[
Self::yz(),
Self::xz(),
Self::xy(),
Self::zy(),
Self::zx(),
Self::yx(),
Self::up(),
Self::down(),
]
}
pub fn in_direction(d: Direction) -> Vector<T> {
use self::Direction::{DOWN, UP, XY, XZ, YX, YZ, ZX, ZY};
match d {
YZ => Self::yz(),
XZ => Self::xz(),
XY => Self::xy(),
ZY => Self::zy(),
ZX => Self::zx(),
YX => Self::yx(),
UP => Self::up(),
DOWN => Self::down(),
}
}
}
impl<T: Zero + One + Neg<Output = T> + Eq> Vector<T> {
pub fn try_into_direction(self) -> Option<Direction> {
use std::convert::TryFrom;
Self::directions()
.iter()
.position(|v| v == &self)
.map(|idx| {
Direction::try_from(idx as u8)
.expect("length of array returned by directions should be 8")
})
}
}
impl<T: Zero + One + Neg<Output = T>> VectorAxial<T> {
pub fn in_direction(d: Direction) -> VectorAxial<T> {
Vector::in_direction(d).into_axial()
}
}
impl<T: Copy + Signed + One + Add + Div> Vector<T> {
pub fn length(&self) -> T {
use num_traits::sign::abs;
(abs(self.x) + abs(self.y) + abs(self.z)) / (T::one() + T::one()) + abs(self.w)
}
}
macro_rules! impl_vector_add {
($type:ident, $delta_type:ident; $( $f:ident ),* ) => {
impl<T, O> Add<$delta_type<T>> for $type<T>
where
T: Add<Output=O>,
{
type Output = $type<O>;
fn add(self, rhs: $delta_type<T>) -> Self::Output {
$type {
$(
$f: self.$f + rhs.$f,
)*
}
}
}
impl<T> AddAssign<$delta_type<T>> for $type<T>
where
T: AddAssign,
{
fn add_assign(&mut self, rhs: $delta_type<T>) {
$(
self.$f += rhs.$f;
)*
}
}
}
}
impl_vector_add!(Point, Vector; x, y, z, w);
impl_vector_add!(Vector, Vector; x, y, z, w);
impl_vector_add!(PointAxial, VectorAxial; x, y, w);
impl_vector_add!(VectorAxial, VectorAxial; x, y, w);
macro_rules! impl_vector_sub {
($type:ident; $( $f:ident ),* ) => {
impl<T, O> Sub<$type<T>> for $type<T>
where
T: Sub<Output=O>,
{
type Output = $type<O>;
fn sub(self, rhs: $type<T>) -> Self::Output {
$type {
$(
$f: self.$f - rhs.$f,
)*
}
}
}
impl<T> SubAssign<$type<T>> for $type<T>
where
T: SubAssign,
{
fn sub_assign(&mut self, rhs: $type<T>) {
$(
self.$f -= rhs.$f;
)*
}
}
};
($type:ident, $delta_type:ident; $( $f:ident ),* ) => {
impl<T, O> Sub<$type<T>> for $type<T>
where
T: Sub<Output=O>,
{
type Output = $delta_type<O>;
fn sub(self, rhs: $type<T>) -> Self::Output {
$delta_type {
$(
$f: self.$f - rhs.$f,
)*
}
}
}
impl<T, O> Sub<$delta_type<T>> for $type<T>
where
T: Sub<Output=O>,
{
type Output = $type<O>;
fn sub(self, rhs: $delta_type<T>) -> Self::Output {
$type {
$(
$f: self.$f - rhs.$f,
)*
}
}
}
impl<T> SubAssign<$delta_type<T>> for $type<T>
where
T: SubAssign,
{
fn sub_assign(&mut self, rhs: $delta_type<T>) {
$(
self.$f -= rhs.$f;
)*
}
}
}
}
impl_vector_sub!(Point, Vector; x, y, z, w);
impl_vector_sub!(Vector; x, y, z, w);
impl_vector_sub!(PointAxial, VectorAxial; x, y, w);
impl_vector_sub!(VectorAxial; x, y, w);
macro_rules! impl_vector_mul {
($type:ident; $( $f:ident ),*) => {
impl<T, O> Mul<T> for $type<T>
where
T: Copy + Mul<Output=O>,
{
type Output = $type<O>;
fn mul(self, rhs: T) -> Self::Output {
$type {
$(
$f: self.$f * rhs,
)*
}
}
}
impl<T> MulAssign<T> for $type<T>
where
T: Copy + MulAssign,
{
fn mul_assign(&mut self, rhs: T) {
$(
self.$f *= rhs;
)*
}
}
}
}
impl_vector_mul!(Vector; x, y, z, w);
impl_vector_mul!(VectorAxial; x, y, w);
impl<T, O> Div<T> for VectorAxial<T>
where
T: Copy + Div<Output = O>,
{
type Output = VectorAxial<O>;
fn div(self, rhs: T) -> Self::Output {
VectorAxial {
x: self.x / rhs,
y: self.y / rhs,
w: self.w / rhs,
}
}
}
impl<T> DivAssign<T> for VectorAxial<T>
where
T: Copy + DivAssign,
{
fn div_assign(&mut self, rhs: T) {
self.x /= rhs;
self.y /= rhs;
self.w /= rhs;
}
}
macro_rules! impl_add_sub_dir {
($type:ident, $vector_type:ident) => {
impl<T> Add<Direction> for $type<T>
where
T: Zero + One + Add + Neg<Output = T>,
{
type Output = $type<T>;
fn add(self, rhs: Direction) -> Self::Output {
self + $vector_type::<T>::in_direction(rhs)
}
}
impl<T> AddAssign<Direction> for $type<T>
where
T: Zero + One + AddAssign<T> + Neg<Output = T>,
{
fn add_assign(&mut self, rhs: Direction) {
*self += $vector_type::<T>::in_direction(rhs);
}
}
impl<T> Sub<Direction> for $type<T>
where
T: Zero + One + Sub<Output = T> + Neg<Output = T>,
{
type Output = $type<T>;
fn sub(self, rhs: Direction) -> Self::Output {
self - $vector_type::<T>::in_direction(rhs)
}
}
impl<T> SubAssign<Direction> for $type<T>
where
T: Zero + One + SubAssign<T> + Neg<Output = T>,
{
fn sub_assign(&mut self, rhs: Direction) {
*self -= $vector_type::<T>::in_direction(rhs);
}
}
};
}
impl_add_sub_dir!(Point, Vector);
impl_add_sub_dir!(PointAxial, VectorAxial);
impl_add_sub_dir!(Vector, Vector);
impl_add_sub_dir!(VectorAxial, VectorAxial);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_ops() {
let mut pt = Point::new(0, 0, 0, 0);
pt += Vector::new(1, 2, -3, 4);
assert_eq!(Point::new(1, 2, -3, 4), pt);
assert_eq!(Point::new(2, 2, -4, 4), pt + Vector::new(1, 0, -1, 0));
pt -= Vector::new(1, -3, 2, 1) - Vector::new(1, 1, -2, 1);
assert_eq!(Point::new(1, 6, -7, 4), pt);
assert_eq!(Point::new(2, 7, -9, 3), pt - Vector::new(-1, -1, 2, 1));
pt += pt.into_vector();
let mut vc = pt - Point::new(2, 10, -12, 6);
assert_eq!(Vector::new(0, 2, -2, 2), vc);
vc += Vector::new(1, -2, 1, -1);
assert_eq!(Vector::new(1, 0, -1, 1), vc);
assert_eq!(Vector::new(2, 2, -4, 5), vc + Vector::new(1, 2, -3, 4));
vc -= Vector::new(1, 1, -2, -3);
assert_eq!(Vector::new(0, -1, 1, 4), vc);
assert_eq!(Vector::new(-2, -2, 4, 3), vc - Vector::new(2, 1, -3, 1));
vc *= 2;
assert_eq!(Vector::new(0, -2, 2, 8), vc);
}
#[test]
fn can_check_validity() {
assert!(Point::try_from_coords(1, 2, -3, 1).is_some());
assert!(Point::try_from_coords(-3, 2, 1, -1).is_some());
assert!(Point::try_from_coords(0, 2, -2, 0).is_some());
assert!(Vector::try_from_coords(1, 2, -3, 1).is_some());
assert!(Vector::try_from_coords(-3, 2, 1, -1).is_some());
assert!(Vector::try_from_coords(0, 2, -2, 0).is_some());
assert!(Point::try_from_coords(1, -2, -3, 1).is_none());
assert!(Point::try_from_coords(-3, 2, -1, -1).is_none());
assert!(Point::try_from_coords(0, 3, -2, 0).is_none());
assert!(Vector::try_from_coords(2, 2, -3, 1).is_none());
assert!(Vector::try_from_coords(-3, -2, 1, -1).is_none());
assert!(Vector::try_from_coords(0, 2, -1, 0).is_none());
}
#[test]
fn can_convert() {
use std::fmt::Debug;
fn test_identity<T>(cube: Point<T>)
where
T: Copy + Eq + Debug + Zero + Sub<Output = T> + AsPrimitive<i64>,
i64: AsPrimitive<T>,
{
let axial: PointAxial<T> = cube.into_axial();
assert_eq!(cube, axial.to_cube());
let cast_axial: PointAxial<i64> = cube.cast_fix().into_axial();
assert_eq!(cube.cast_fix(), cast_axial.to_cube());
}
fn test_vec_identity<T>(cube: Vector<T>)
where
T: Copy + Eq + Debug + Zero + Sub<Output = T> + AsPrimitive<i64>,
i64: AsPrimitive<T>,
{
let axial: VectorAxial<T> = cube.into_axial();
assert_eq!(cube, axial.to_cube());
let cast_axial: VectorAxial<i64> = cube.cast_fix().into_axial();
assert_eq!(cube.cast_fix(), cast_axial.to_cube());
}
test_identity(Point::new(1, 2, -3, 4));
test_identity(Point::new(-1, 4, -3, 4));
test_identity(Point::new(2, 1, -3, -2));
test_identity(Point::new(-6, 7, -1, -2));
test_vec_identity(Vector::new(1, 2, -3, 4));
test_vec_identity(Vector::new(-1, 4, -3, 4));
test_vec_identity(Vector::new(2, 1, -3, -2));
test_vec_identity(Vector::new(-6, 7, -1, -2));
}
#[test]
fn can_round() {
assert_eq!(
Point::new(1, 2, -3, 4),
Point::new(1.4, 2.2, -3.6, 4.2).cast_round()
);
assert_eq!(
Point::new(1, 2, -3, 4),
Point::new(0.6, 2.0, -2.6, 3.6).cast_round()
);
assert_eq!(
Point::new(1, 2, -3, 4),
Point::new(0.6, 2.4, -3.0, 3.6).cast_round()
);
assert_eq!(
Vector::new(1, 2, -3, 4),
Vector::new(1.4, 2.2, -3.6, 4.2).cast_round()
);
assert_eq!(
Vector::new(1, 2, -3, 4),
Vector::new(0.6, 2.0, -2.6, 3.6).cast_round()
);
assert_eq!(
Vector::new(1, 2, -3, 4),
Vector::new(0.6, 2.4, -3.0, 3.6).cast_round()
);
assert_eq!(
Vector::new(1, 0, -1, 0),
Vector::new(0.4, 0.3, -0.7, 0.0).cast_round()
);
assert_eq!(
PointAxial::new(1, 2, 4),
PointAxial::new(1.4, 2.2, 4.2).round().cast()
);
assert_eq!(
PointAxial::new(1, 2, 4),
PointAxial::new(0.6, 2.0, 3.6).round().cast()
);
assert_eq!(
PointAxial::new(1, 2, 4),
PointAxial::new(0.6, 2.4, 3.6).round().cast()
);
assert_eq!(
VectorAxial::new(1, 2, 4),
VectorAxial::new(1.4, 2.2, 4.2).round().cast()
);
assert_eq!(
VectorAxial::new(1, 2, 4),
VectorAxial::new(0.6, 2.0, 3.6).round().cast()
);
assert_eq!(
VectorAxial::new(1, 2, 4),
VectorAxial::new(0.6, 2.4, 3.6).round().cast()
);
}
#[test]
fn can_calculate_length() {
assert_eq!(3, Vector::new(1, 2, -3, 0).length());
assert_eq!(5, Vector::new(1, -4, 3, -1).length());
assert_eq!(8, Vector::new(7, -4, -3, 1).length());
assert_eq!(1, Point::new(0, 0, 0, 0).distance(Point::new(1, -1, 0, 0)));
assert_eq!(3, Point::new(1, 0, -1, 0).distance(Point::new(0, 2, -2, 1)));
assert_eq!(
5,
Point::new(1, 0, -1, -1).distance(Point::new(2, 2, -4, -3))
);
}
#[test]
fn can_iter_directions() {
let reference = {
use self::Direction as D;
vec![D::YZ, D::XZ, D::XY, D::ZY, D::ZX, D::YX, D::UP, D::DOWN]
};
assert_eq!(reference, Direction::iter().collect::<Vec<_>>());
}
#[test]
fn can_neg_directions() {
for dir in Direction::iter() {
let sum = Vector::<i16>::in_direction(dir) + Vector::in_direction(-dir);
assert_eq!(Vector::zero(), sum);
}
}
#[test]
fn can_get_direction_from_vector() {
for dir in Direction::iter() {
assert_eq!(
Some(dir),
Vector::<i16>::in_direction(dir).try_into_direction()
);
}
}
}