use std::iter::{Extend, FromIterator, FusedIterator, Iterator};
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
use num_traits::{AsPrimitive, One, Zero};
use super::coords::{Point, PointAxial, Vector, VectorAxial};
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
pub struct QuadPrism<T> {
pub low: PointAxial<T>,
pub high: PointAxial<T>,
}
fn sort2<T: PartialOrd>(a: T, b: T) -> (T, T) {
use std::cmp::Ordering as O;
match a.partial_cmp(&b) {
Some(O::Less) | Some(O::Equal) => (a, b),
Some(O::Greater) | None => (b, a),
}
}
fn sort2_ref<'a, T: PartialOrd>(a: &'a T, b: &'a T) -> (&'a T, &'a T) {
use std::cmp::Ordering as O;
match a.partial_cmp(b) {
Some(O::Less) | Some(O::Equal) => (a, b),
Some(O::Greater) | None => (b, a),
}
}
fn bounds<T: PartialOrd>(a: PointAxial<T>, b: PointAxial<T>) -> (PointAxial<T>, PointAxial<T>) {
let (min_x, max_x) = sort2(a.x, b.x);
let (min_y, max_y) = sort2(a.y, b.y);
let (min_w, max_w) = sort2(a.w, b.w);
(
PointAxial {
x: min_x,
y: min_y,
w: min_w,
},
PointAxial {
x: max_x,
y: max_y,
w: max_w,
},
)
}
fn bounds_in_place<T: PartialOrd>(a: &mut PointAxial<T>, b: &mut PointAxial<T>) {
if a.x > b.x {
std::mem::swap(&mut a.x, &mut b.x);
}
if a.y > b.y {
std::mem::swap(&mut a.y, &mut b.y);
}
if a.w > b.w {
std::mem::swap(&mut a.w, &mut b.w);
}
}
fn clamp<T: PartialOrd>(low: T, high: T, val: T) -> T {
if val < low {
low
} else if val > high {
high
} else {
val
}
}
impl<T: PartialOrd + One + Add<Output = T>> QuadPrism<T> {
pub fn new(low: PointAxial<T>, high: PointAxial<T>) -> Self {
QuadPrism { low, high }
}
pub fn from_points_axial(a: PointAxial<T>, b: PointAxial<T>) -> Self {
let (low, high) = bounds(a, b);
QuadPrism {
low,
high: high + VectorAxial::one(),
}
}
pub fn from_points(a: Point<T>, b: Point<T>) -> Self {
Self::from_points_axial(a.into_axial(), b.into_axial())
}
pub fn from_base_size_axial(base: PointAxial<T>, size: VectorAxial<T>) -> Self
where
T: Copy + Zero + One + AddAssign + SubAssign + Neg<Output = T>,
{
use std::cmp::Ordering as O;
let mut canon_base = base;
let mut canon_size = size;
macro_rules! canonicalize_comp {
($f:ident) => {
if let Some(O::Less) = size.$f.partial_cmp(&T::zero()) {
canon_size.$f += T::one();
canon_base.$f += canon_size.$f;
canon_size.$f = -canon_size.$f + T::one();
}
};
}
canonicalize_comp!(x);
canonicalize_comp!(y);
canonicalize_comp!(w);
let (low, high) = bounds(canon_base, canon_base + canon_size);
QuadPrism { low, high }
}
pub fn from_base_size(base: Point<T>, size: Vector<T>) -> Self
where
T: Copy + Zero + One + AddAssign + SubAssign + Neg<Output = T>,
{
Self::from_base_size_axial(base.into_axial(), size.into_axial())
}
pub fn from_center_size_axial(mut center: PointAxial<T>, size: VectorAxial<T>) -> Self
where
T: Copy + Zero + One + AddAssign + SubAssign + Neg<Output = T> + Div<Output = T>,
{
let half = size / (T::one() + T::one());
center -= half;
Self::from_base_size_axial(center, size)
}
pub fn from_center_size(center: Point<T>, size: Vector<T>) -> Self
where
T: Copy + Zero + One + AddAssign + SubAssign + Neg<Output = T> + Div<Output = T>,
{
Self::from_center_size_axial(center.into_axial(), size.into_axial())
}
pub fn expand_into_point_axial(&mut self, mut point: PointAxial<T>) {
bounds_in_place(&mut self.low, &mut point);
point = point + VectorAxial::one();
bounds_in_place(&mut point, &mut self.high);
}
pub fn expand_into_point(&mut self, point: Point<T>) {
self.expand_into_point_axial(point.into_axial());
}
pub fn column(self, x: T) -> Option<Self>
where
T: Copy,
{
if x >= self.low.x && x < self.high.x {
Some(QuadPrism {
low: PointAxial { x, ..self.low },
high: PointAxial {
x: x + T::one(),
..self.high
},
})
} else {
None
}
}
pub fn row(self, y: T) -> Option<Self>
where
T: Copy,
{
if y >= self.low.y && y < self.high.y {
Some(QuadPrism {
low: PointAxial { y, ..self.low },
high: PointAxial {
y: y + T::one(),
..self.high
},
})
} else {
None
}
}
pub fn layer(self, w: T) -> Option<Self>
where
T: Copy,
{
if w >= self.low.w && w < self.high.w {
Some(QuadPrism {
low: PointAxial { w, ..self.low },
high: PointAxial {
w: w + T::one(),
..self.high
},
})
} else {
None
}
}
}
impl<T: Copy + PartialOrd + Mul<Output = T> + Zero + Sub<Output = T>> QuadPrism<T> {
pub fn size_axial(&self) -> VectorAxial<T> {
let v = self.high - self.low;
VectorAxial {
x: sort2(T::zero(), v.x).1,
y: sort2(T::zero(), v.y).1,
w: sort2(T::zero(), v.w).1,
}
}
pub fn size(&self) -> Vector<T> {
self.size_axial().to_cube()
}
pub fn volume(&self) -> i64
where
T: AsPrimitive<i64>,
{
let size = self.size_axial().cast();
size.x * size.y * size.w
}
pub fn center_axial(&self) -> PointAxial<T>
where
T: One + Div<Output = T>,
{
let two = T::one() + T::one();
PointAxial {
x: (self.low.x + self.high.x - T::one()) / two,
y: (self.low.y + self.high.y - T::one()) / two,
w: (self.low.w + self.high.w - T::one()) / two,
}
}
pub fn center(&self) -> Point<T>
where
T: One + Div<Output = T>,
{
self.center_axial().to_cube()
}
}
impl<T: PartialOrd> QuadPrism<T> {
pub fn is_empty(&self) -> bool {
self.high.x <= self.low.x || self.high.y <= self.low.y || self.high.w <= self.low.w
}
pub fn contains_axial(&self, pt: &PointAxial<T>) -> bool {
pt.x >= self.low.x
&& pt.y >= self.low.y
&& pt.w >= self.low.w
&& pt.x < self.high.x
&& pt.y < self.high.y
&& pt.w < self.high.w
}
pub fn contains(&self, pt: &Point<T>) -> bool {
*pt.x() >= self.low.x
&& *pt.y() >= self.low.y
&& *pt.w() >= self.low.w
&& *pt.x() < self.high.x
&& *pt.y() < self.high.y
&& *pt.w() < self.high.w
}
pub fn intersection(self, other: QuadPrism<T>) -> QuadPrism<T> {
let (_, low) = bounds(self.low, other.low);
let (high, _) = bounds(self.high, other.high);
QuadPrism { low, high }
}
pub fn intersects(&self, other: &QuadPrism<T>) -> bool {
sort2_ref(&self.low.x, &other.low.x).1 < sort2_ref(&self.high.x, &other.high.x).0
&& sort2_ref(&self.low.y, &other.low.y).1 < sort2_ref(&self.high.y, &other.high.y).0
&& sort2_ref(&self.low.w, &other.low.w).1 < sort2_ref(&self.high.w, &other.high.w).0
}
pub fn covers(&self, other: &QuadPrism<T>) -> bool
where
T: PartialEq,
{
sort2_ref(&self.low.x, &other.low.x).1 == &other.low.x
&& sort2_ref(&self.low.y, &other.low.y).1 == &other.low.y
&& sort2_ref(&self.low.w, &other.low.w).1 == &other.low.w
&& sort2_ref(&self.high.x, &other.high.x).0 == &other.high.x
&& sort2_ref(&self.high.y, &other.high.y).0 == &other.high.y
&& sort2_ref(&self.high.w, &other.high.w).0 == &other.high.w
}
pub fn union(self, other: QuadPrism<T>) -> QuadPrism<T> {
let (low, _) = bounds(self.low, other.low);
let (_, high) = bounds(self.high, other.high);
QuadPrism { low, high }
}
pub fn union_short_circuit(self, other: QuadPrism<T>) -> QuadPrism<T> {
if self.is_empty() {
other
} else if other.is_empty() {
self
} else {
self.union(other)
}
}
pub fn clamp(self, v: Point<T>) -> Option<Point<T>>
where
T: Copy + Sub<Output = T> + One + Zero,
{
self.clamp_axial(v.into_axial()).map(PointAxial::to_cube)
}
pub fn clamp_axial(self, v: PointAxial<T>) -> Option<PointAxial<T>>
where
T: Sub<Output = T> + One,
{
if self.is_empty() {
return None;
}
Some(PointAxial {
x: clamp(self.low.x, self.high.x - T::one(), v.x),
y: clamp(self.low.y, self.high.y - T::one(), v.y),
w: clamp(self.low.w, self.high.w - T::one(), v.w),
})
}
pub fn points_axial(&self) -> PointsAxial<T>
where
T: Copy + PartialOrd + AddAssign + One,
{
PointsAxial {
prism: self,
current: self.low,
}
}
pub fn points(&self) -> Points<T>
where
T: Copy + PartialOrd + AddAssign + One,
{
Points(PointsAxial {
prism: self,
current: self.low,
})
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct PointsAxial<'a, T> {
prism: &'a QuadPrism<T>,
current: PointAxial<T>,
}
impl<'a, T> Iterator for PointsAxial<'a, T>
where
T: Copy + PartialOrd + AddAssign + One,
{
type Item = PointAxial<T>;
fn next(&mut self) -> Option<Self::Item> {
if !self.prism.contains_axial(&self.current) {
return None;
}
let result = self.current;
self.current.x += T::one();
if self.current.x >= self.prism.high.x {
self.current.x = self.prism.low.x;
self.current.y += T::one();
}
if self.current.y >= self.prism.high.y {
self.current.y = self.prism.low.y;
self.current.w += T::one();
}
Some(result)
}
}
impl<'a, T> FusedIterator for PointsAxial<'a, T> where T: Copy + PartialOrd + AddAssign + One {}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct Points<'a, T>(PointsAxial<'a, T>);
impl<'a, T> Iterator for Points<'a, T>
where
T: Copy + PartialOrd + Sub<Output = T> + AddAssign + Zero + One,
{
type Item = Point<T>;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(PointAxial::to_cube)
}
}
impl<'a, T> FusedIterator for Points<'a, T> where
T: Copy + PartialOrd + Sub<Output = T> + AddAssign + Zero + One
{
}
impl<T> Extend<PointAxial<T>> for QuadPrism<T>
where
T: PartialOrd + One + Add<Output = T>,
{
fn extend<I: IntoIterator<Item = PointAxial<T>>>(&mut self, it: I) {
for pt in it {
self.expand_into_point_axial(pt);
}
}
}
impl<T> Extend<Point<T>> for QuadPrism<T>
where
T: PartialOrd + One + Add<Output = T>,
{
fn extend<I: IntoIterator<Item = Point<T>>>(&mut self, it: I) {
for pt in it {
self.expand_into_point(pt);
}
}
}
impl<T> FromIterator<PointAxial<T>> for QuadPrism<T>
where
T: Copy + Default + PartialOrd + One + Add<Output = T>,
{
fn from_iter<I: IntoIterator<Item = PointAxial<T>>>(it: I) -> Self {
let mut iter = it.into_iter();
let mut prism = {
if let Some(first) = iter.next() {
QuadPrism::from_points_axial(first, first)
} else {
return QuadPrism::default();
}
};
prism.extend(iter);
prism
}
}
impl<T> FromIterator<Point<T>> for QuadPrism<T>
where
T: Copy + Default + PartialOrd + One + Add<Output = T>,
{
fn from_iter<I: IntoIterator<Item = Point<T>>>(it: I) -> Self {
it.into_iter().map(Point::into_axial).collect()
}
}
#[cfg(feature = "rand-07")]
mod distribution_impl {
use super::{Point, PointAxial, QuadPrism};
use std::ops::Sub;
use num_traits::Zero;
use rand::distributions::{uniform::SampleUniform, Distribution, Uniform};
use rand::Rng;
impl<T> Distribution<Point<T>> for QuadPrism<T>
where
T: Copy + Zero + Sub<Output = T> + SampleUniform,
{
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Point<T> {
let axial: PointAxial<_> = rng.sample(self);
axial.to_cube()
}
}
impl<T> Distribution<PointAxial<T>> for QuadPrism<T>
where
T: Copy + SampleUniform,
{
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> PointAxial<T> {
let x = Uniform::new(self.low.x, self.high.x).sample(rng);
let y = Uniform::new(self.low.y, self.high.y).sample(rng);
let w = Uniform::new(self.low.w, self.high.w).sample(rng);
PointAxial::new(x, y, w)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
type HA = super::super::coords::PointAxial<i16>;
type VA = super::super::coords::VectorAxial<i16>;
#[test]
fn can_construct_from_points() {
fn test_from_points_sanity(a: HA, b: HA, expected_volume: i64) {
let from_axial = QuadPrism::from_points_axial(a, b);
let from_cube = QuadPrism::from_points(a.to_cube(), b.to_cube());
let from_expand = {
let mut q = QuadPrism::from_points_axial(a, a);
q.expand_into_point_axial(b);
q
};
let from_expand_cube = {
let mut q = QuadPrism::from_points(a.to_cube(), a.to_cube());
q.expand_into_point(b.to_cube());
q
};
assert_eq!(from_axial, from_cube);
assert_eq!(from_axial, from_expand);
assert_eq!(from_axial, from_expand_cube);
assert!(from_axial.contains_axial(&a));
assert!(from_axial.contains_axial(&b));
assert!(from_cube.contains_axial(&a));
assert!(from_cube.contains_axial(&b));
assert!(from_axial.contains(&a.to_cube()));
assert!(from_axial.contains(&b.to_cube()));
assert!(from_cube.contains(&a.to_cube()));
assert!(from_cube.contains(&b.to_cube()));
assert_eq!(expected_volume, from_axial.volume());
assert_eq!(expected_volume, from_cube.volume());
}
test_from_points_sanity(HA::new(0, 0, 0), HA::new(0, 0, 0), 1);
test_from_points_sanity(HA::new(1, 2, 3), HA::new(-1, -2, -3), 3 * 5 * 7);
test_from_points_sanity(HA::new(-1, 0, 1), HA::new(2, 1, -1), 4 * 2 * 3);
test_from_points_sanity(HA::new(1, -1, 5), HA::new(0, -1, 6), 2 * 2);
}
#[test]
fn can_construct_from_base_size() {
fn test_from_base_size_sanity(base: HA, size: VA, expected_volume: i64) {
let from_axial = QuadPrism::from_base_size_axial(base, size);
let from_cube = QuadPrism::from_base_size(base.to_cube(), size.to_cube());
assert_eq!(from_axial, from_cube);
if expected_volume > 0 {
assert!(from_axial.contains_axial(&base));
assert!(!from_axial.contains_axial(&(base + size)));
assert!(from_cube.contains_axial(&base));
assert!(!from_cube.contains_axial(&(base + size)));
assert!(from_axial.contains(&base.to_cube()));
assert!(!from_axial.contains(&(base + size).to_cube()));
assert!(from_cube.contains(&base.to_cube()));
assert!(!from_cube.contains(&(base + size).to_cube()));
}
assert_eq!(expected_volume, from_axial.volume());
assert_eq!(expected_volume, from_cube.volume());
}
test_from_base_size_sanity(HA::new(0, 0, 0), VA::new(-1, 2, 1), 2);
test_from_base_size_sanity(HA::new(1, 2, 3), VA::new(-7, 0, 2), 0);
test_from_base_size_sanity(HA::new(-1, 0, 1), VA::new(3, -7, -2), 3 * 7 * 2);
test_from_base_size_sanity(HA::new(1, -1, 5), VA::new(5, 1, -5), 5 * 5);
}
#[test]
fn can_construct_from_center_size() {
assert_eq!(
QuadPrism::from_base_size_axial(HA::new(1, 1, 1), VA::new(3, 3, 1)),
QuadPrism::from_center_size_axial(HA::new(2, 2, 1), VA::new(3, 3, 1)),
);
assert_eq!(
QuadPrism::from_base_size(HA::new(1, 1, 1).to_cube(), VA::new(3, 3, 1).to_cube()),
QuadPrism::from_center_size(HA::new(2, 2, 1).to_cube(), VA::new(3, 3, 1).to_cube()),
);
}
#[test]
fn can_intersect() {
fn test_intersection(
a: QuadPrism<i16>,
b: QuadPrism<i16>,
expected: Option<QuadPrism<i16>>,
) {
let intersection_a = a.intersection(b);
let intersection_b = b.intersection(a);
assert_eq!(intersection_a, intersection_b);
if let Some(exp) = expected {
assert_eq!(exp, intersection_a);
assert!(!intersection_a.is_empty());
} else {
assert!(intersection_a.is_empty());
}
assert_eq!(expected.is_some(), a.intersects(&b));
assert_eq!(expected.is_some(), b.intersects(&a));
}
test_intersection(
QuadPrism::from_points_axial(HA::new(0, 0, 0), HA::new(3, 3, 0)),
QuadPrism::from_points_axial(HA::new(-3, -3, 0), HA::new(0, 0, 0)),
Some(QuadPrism::from_points_axial(
HA::new(0, 0, 0),
HA::new(0, 0, 0),
)),
);
test_intersection(
QuadPrism::from_points_axial(HA::new(-2, -1, -3), HA::new(3, 3, 3)),
QuadPrism::from_points_axial(HA::new(-3, -3, 2), HA::new(2, 1, -2)),
Some(QuadPrism::from_points_axial(
HA::new(-2, -1, -2),
HA::new(2, 1, 2),
)),
);
test_intersection(
QuadPrism::from_points_axial(HA::new(2, 2, 0), HA::new(3, 3, 0)),
QuadPrism::from_points_axial(HA::new(-3, -3, 0), HA::new(-2, -2, 0)),
None,
);
}
#[test]
fn can_cover() {
fn test_cover(
expected: bool,
expected_reverse: bool,
a: QuadPrism<i16>,
b: QuadPrism<i16>,
) {
assert_eq!(expected, a.covers(&b));
assert_eq!(expected_reverse, b.covers(&a));
if expected {
assert_eq!(b, a.intersection(b));
}
if expected_reverse {
assert_eq!(a, b.intersection(a));
}
}
test_cover(
true,
true,
QuadPrism::from_points_axial(HA::new(0, 0, 0), HA::new(1, 1, 1)),
QuadPrism::from_points_axial(HA::new(1, 1, 1), HA::new(0, 0, 0)),
);
test_cover(
true,
false,
QuadPrism::from_points_axial(HA::new(-5, -6, -7), HA::new(5, 6, 7)),
QuadPrism::from_points_axial(HA::new(-2, -3, -2), HA::new(1, 1, 1)),
);
test_cover(
false,
false,
QuadPrism::from_points_axial(HA::new(-2, -5, -2), HA::new(1, 4, 1)),
QuadPrism::from_points_axial(HA::new(-5, -1, -7), HA::new(5, 6, 3)),
);
test_cover(
true,
false,
QuadPrism::from_points_axial(HA::new(-5, -6, -7), HA::new(5, 6, 7)),
QuadPrism::from_points_axial(HA::new(-2, -2, -2), HA::new(-1, -1, -1)).intersection(
QuadPrism::from_points_axial(HA::new(2, 2, 2), HA::new(1, 1, 1)),
),
);
}
#[test]
fn can_iter() {
fn test_iter(qp: QuadPrism<i16>, points: &[HA]) {
itertools::assert_equal(qp.points_axial(), points.iter().cloned());
itertools::assert_equal(qp.points(), points.iter().cloned().map(HA::to_cube));
}
test_iter(
QuadPrism::from_points_axial(HA::new(-1, -1, 0), HA::new(0, 0, 1)),
&[
HA::new(-1, -1, 0),
HA::new(0, -1, 0),
HA::new(-1, 0, 0),
HA::new(0, 0, 0),
HA::new(-1, -1, 1),
HA::new(0, -1, 1),
HA::new(-1, 0, 1),
HA::new(0, 0, 1),
],
);
test_iter(
QuadPrism::from_base_size_axial(HA::new(0, 0, 0), VA::new(0, 0, 0)),
&[],
);
test_iter(
QuadPrism::from_base_size_axial(HA::new(0, 0, 0), VA::new(1, 1, 1)),
&[HA::new(0, 0, 0)],
);
}
#[test]
fn can_union() {
fn test_union(
a: QuadPrism<i16>,
b: QuadPrism<i16>,
expected: QuadPrism<i16>,
short_circuit_expected: Option<QuadPrism<i16>>,
) {
let union_a = a.union(b);
let union_b = b.union(a);
assert_eq!(expected, union_a);
assert_eq!(union_a, union_b);
assert!(union_a.covers(&a));
assert!(union_a.covers(&b));
let union_sc_a = a.union_short_circuit(b);
let union_sc_b = b.union_short_circuit(a);
if let Some(exp) = short_circuit_expected {
assert_eq!(exp, union_sc_a);
assert_eq!(union_sc_a, union_sc_b);
assert!(!union_sc_a.is_empty());
} else {
assert!(union_sc_a.is_empty());
assert!(union_sc_b.is_empty());
}
}
test_union(
QuadPrism::from_base_size_axial(HA::new(-3, -3, -2), VA::zero()),
QuadPrism::from_base_size_axial(HA::new(3, 3, 2), VA::zero()),
QuadPrism::from_points_axial(HA::new(-3, -3, -2), HA::new(2, 2, 1)),
None,
);
test_union(
QuadPrism::default(),
QuadPrism::from_points_axial(HA::new(-3, -3, -3), HA::new(-2, -2, -2)),
QuadPrism::from_points_axial(HA::new(-3, -3, -3), HA::new(-1, -1, -1)),
Some(QuadPrism::from_points_axial(
HA::new(-3, -3, -3),
HA::new(-2, -2, -2),
)),
);
test_union(
QuadPrism::from_points_axial(HA::new(-2, -1, -3), HA::new(3, 3, 3)),
QuadPrism::from_points_axial(HA::new(-3, -3, 2), HA::new(2, 1, -2)),
QuadPrism::from_points_axial(HA::new(-3, -3, -3), HA::new(3, 3, 3)),
Some(QuadPrism::from_points_axial(
HA::new(-3, -3, -3),
HA::new(3, 3, 3),
)),
);
}
#[test]
fn can_get_row_column_layer() {
let prism = QuadPrism::from_points_axial(HA::new(-2, -3, -4), HA::new(1, 2, 3));
assert_eq!(None, prism.column(-3));
assert_eq!(None, prism.column(2));
assert_eq!(
Some(QuadPrism::from_points_axial(
HA::new(-1, -3, -4),
HA::new(-1, 2, 3)
)),
prism.column(-1),
);
assert_eq!(None, prism.row(-4));
assert_eq!(None, prism.row(3));
assert_eq!(
Some(QuadPrism::from_points_axial(
HA::new(-2, 1, -4),
HA::new(1, 1, 3)
)),
prism.row(1),
);
assert_eq!(None, prism.layer(-5));
assert_eq!(None, prism.layer(4));
assert_eq!(
Some(QuadPrism::from_points_axial(
HA::new(-2, -3, 2),
HA::new(1, 2, 2)
)),
prism.layer(2),
);
}
#[test]
fn can_clamp() {
let prism = QuadPrism::from_points_axial(HA::new(-2, -3, -4), HA::new(1, 2, 3));
assert_eq!(
Some(HA::new(-2, 2, -4)),
prism.clamp_axial(HA::new(-3, 3, -5))
);
assert_eq!(
Some(HA::new(1, -3, 3)),
prism.clamp_axial(HA::new(2, -4, 4))
);
assert_eq!(
Some(HA::new(-1, 0, 1)),
prism.clamp_axial(HA::new(-1, 0, 1))
);
let prism = QuadPrism::default();
assert_eq!(None, prism.clamp_axial(HA::new(0, 0, 0)));
assert_eq!(None, prism.clamp_axial(HA::new(1, -1, 2)));
}
}