#![crate_name = "hex2d"]
#![crate_type = "lib"]
#![warn(missing_docs)]
extern crate num;
extern crate rand;
extern crate rustc_serialize;
use num::{Float, One, Zero};
use num::iter::range_inclusive;
use std::ops::{Add, Sub, Neg};
use std::cmp::{max, min};
pub use Direction::*;
pub use Angle::*;
use Spin::*;
use Spacing::*;
pub trait Integer : num::Signed +
num::Integer +
num::CheckedAdd +
num::ToPrimitive +
num::FromPrimitive +
One + Zero + Copy { }
impl<I> Integer for I
where
I : num::Signed +
num::Integer +
num::CheckedAdd +
num::ToPrimitive +
num::FromPrimitive +
One + Zero + Copy { }
#[cfg(test)]
mod test;
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd, RustcDecodable)]
pub struct Coordinate<I : Integer = i32> {
pub x : I,
pub y : I,
}
pub trait ToCoordinate<I: Integer = i32> {
fn to_coordinate(&self) -> Coordinate<I>;
}
pub trait ToDirection {
fn to_direction(&self) -> Direction;
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd, RustcDecodable)]
pub struct Position<I : Integer = i32> {
pub coord : Coordinate<I>,
pub dir : Direction,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd, RustcDecodable)]
pub enum Direction {
YZ = 0,
XZ,
XY,
ZY,
ZX,
YX,
}
static ALL_DIRECTIONS : [Direction; 6] = [ YZ, XZ, XY, ZY, ZX, YX ];
static ALL_ANGLES : [Angle; 6] = [ Forward, Right, RightBack, Back, LeftBack, Left];
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd, RustcDecodable)]
pub enum Angle {
Forward = 0,
Right,
RightBack,
Back,
LeftBack,
Left,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd, RustcDecodable)]
pub enum Spin {
CW(Direction),
CCW(Direction),
}
#[derive(Copy, Clone, PartialEq, Debug, PartialOrd, RustcDecodable)]
pub enum Spacing {
FlatTop(f32),
PointyTop(f32),
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Ord, PartialOrd, RustcDecodable)]
pub enum IntegerSpacing<I> {
FlatTop(I, I),
PointyTop(I, I),
}
impl<I : Integer> Coordinate<I> {
pub fn new(x : I, y : I) -> Coordinate<I> {
Coordinate { x: x, y: y}
}
pub fn from_round(x : f32, y : f32) -> Coordinate<I> {
Coordinate::nearest(x, y)
}
pub fn nearest(x : f32, y : f32) -> Coordinate<I> {
let z = 0f32 - x - y;
let mut rx = x.round();
let mut ry = y.round();
let rz = z.round();
let x_diff = (rx - x).abs();
let y_diff = (ry - y).abs();
let z_diff = (rz - z).abs();
if x_diff > y_diff && x_diff > z_diff {
rx = -ry - rz;
} else if y_diff > z_diff {
ry = -rx - rz;
} else {
}
Coordinate {
x: num::FromPrimitive::from_f32(rx).unwrap(),
y: num::FromPrimitive::from_f32(ry).unwrap(),
}
}
pub fn from_round_lossy(x : f32, y : f32) -> Option<Coordinate<I>> {
Coordinate::nearest_lossy(x, y)
}
pub fn nearest_lossy(x : f32, y : f32) -> Option<Coordinate<I>> {
let z = 0f32 - x - y;
let mut rx = x.round();
let mut ry = y.round();
let mut rz = z.round();
let x_diff = (rx - x).abs();
let y_diff = (ry - y).abs();
let z_diff = (rz - z).abs();
if x_diff > y_diff && x_diff > z_diff {
rx = -ry - rz;
} else if y_diff > z_diff {
ry = -rx - rz;
} else {
rz = -rx - ry;
}
let x_diff = (rx - x).abs();
let y_diff = (ry - y).abs();
let z_diff = (rz - z).abs();
if x_diff + y_diff + z_diff > 0.99 {
return None;
}
Some(Coordinate {
x: num::FromPrimitive::from_f32(rx).unwrap(),
y: num::FromPrimitive::from_f32(ry).unwrap(),
})
}
pub fn from_pixel_integer(spacing : IntegerSpacing<I>, v : (I, I)) -> (Coordinate<I>, (I, I)) {
Coordinate::nearest_with_offset(spacing, v)
}
pub fn from_pixel(x: f32, y: f32, spacing: Spacing) -> Coordinate<I> {
match spacing {
Spacing::PointyTop(size) => {
let q = (x * 3f32.sqrt()/3f32 - y / 3f32) / size;
let r = y * 2f32/3f32 / size;
return Coordinate::nearest(q, -r -q);
},
Spacing::FlatTop(size) => {
let q = x * 2f32/3f32 / size;
let r = (-x / 3f32 + 3f32.sqrt()/3f32 * y) / size;
return Coordinate::nearest(q, -r -q);
}
}
}
pub fn nearest_with_offset(spacing : IntegerSpacing<I>, v : (I, I)) -> (Coordinate<I>, (I, I)) {
let (asc_x, asc_y) = v;
let two : I = num::FromPrimitive::from_i8(2).unwrap();
let ((q, qo),(r, ro)) = match spacing {
IntegerSpacing::FlatTop(w, h) => (
(asc_x.div_floor(&w), asc_x.mod_floor(&w)),
(
(asc_y - h * asc_x.div_floor(&w) / two).div_floor(&h),
(asc_y + h / two * asc_x.div_floor(&w)).mod_floor(&h)
)
),
IntegerSpacing::PointyTop(w, h) => (
(
(asc_x - w * asc_y.div_floor(&h) / two).div_floor(&w),
(asc_x + w / two * asc_y.div_floor(&h)).mod_floor(&w)
),
(asc_y.div_floor(&h), asc_y.mod_floor(&h))
),
};
let coord = Coordinate{ x: q, y: -q - r };
(coord, (qo, ro))
}
pub fn to_pixel_float(&self, spacing : Spacing) -> (f32, f32) {
self.to_pixel(spacing)
}
pub fn to_pixel(&self, spacing : Spacing) -> (f32, f32) {
let q = self.x.to_f32().unwrap();
let r = self.z().to_f32().unwrap();
match spacing {
FlatTop(s) => (
s * 3f32 / 2f32 * q,
s * 3f32.sqrt() * (r + q / 2f32)
),
PointyTop(s) => (
s * 3f32.sqrt() * (q + r / 2f32),
s * 3f32 / 2f32 * r
)
}
}
pub fn to_pixel_integer(&self, spacing : IntegerSpacing<I>) -> (I, I) {
let q = self.x;
let r = self.z();
let two = num::FromPrimitive::from_i8(2).unwrap();
match spacing {
IntegerSpacing::FlatTop(w, h) => (
w * q,
h * (r + r + q) / two
),
IntegerSpacing::PointyTop(w, h) => (
w * (q + q + r) / two,
h * r
)
}
}
pub fn scale(&self, s : I) -> Coordinate<I> {
let x = self.x * s;
let y = self.y * s;
Coordinate{ x: x, y: y }
}
pub fn neighbors(&self) -> [Coordinate<I>; 6] {
[
*self + YZ,
*self + XZ,
*self + XY,
*self + ZY,
*self + ZX,
*self + YX,
]
}
pub fn rotate_around_zero(&self, a : Angle) -> Coordinate<I> {
let (x, y, z) = (self.x, self.y, self.z());
let (x, y) = match a {
Forward => (x, y),
Right => (-z, -x),
RightBack => (y, z),
Back => (-x, -y),
LeftBack => (z, x),
Left => (-y, -z),
};
Coordinate{ x: x, y: y}
}
pub fn rotate_around(&self, center : Coordinate<I>, a : Angle) -> Coordinate<I> {
let rel_p = *self - center;
let rot_p = rel_p.rotate_around_zero(a);
rot_p + center
}
pub fn for_each_in_line_to<F>(&self, dest : Coordinate<I>, mut f : F)
where
F : FnMut(Coordinate<I>),
for <'a> &'a I: Add<&'a I, Output = I>
{
if *self == dest {
f(*self);
return;
}
let n = self.distance(dest);
let ax = self.x.to_f32().unwrap();
let ay = self.y.to_f32().unwrap();
let bx = dest.x.to_f32().unwrap();
let by = dest.y.to_f32().unwrap();
for i in range_inclusive(Zero::zero(), n) {
let d = i.to_f32().unwrap() / n.to_f32().unwrap();
let x = ax + (bx - ax) * d;
let y = ay + (by - ay) * d;
let c = Coordinate::from_round(x, y);
f(c);
}
}
pub fn for_each_in_line_to_lossy<F>(&self, dest : Coordinate<I>, mut f : F)
where
F : FnMut(Coordinate<I>),
for <'a> &'a I: Add<&'a I, Output = I>
{
if *self == dest {
f(*self);
return;
}
let n = self.distance(dest);
let ax = self.x.to_f32().unwrap();
let ay = self.y.to_f32().unwrap();
let bx = dest.x.to_f32().unwrap();
let by = dest.y.to_f32().unwrap();
for i in range_inclusive(Zero::zero(), n) {
let d = i.to_f32().unwrap() / n.to_f32().unwrap();
let x = ax + (bx - ax) * d;
let y = ay + (by - ay) * d;
let c = Coordinate::from_round_lossy(x, y);
if let Some(c) = c {
f(c);
}
}
}
pub fn for_each_in_line_to_with_edge_detection<F>(&self, dest : Coordinate<I>, mut f : F)
where
F : FnMut((Coordinate<I>, Coordinate<I>)),
for <'a> &'a I: Add<&'a I, Output = I>
{
if *self == dest {
f((*self, *self));
return;
}
let n = self.distance(dest);
let ax = self.x.to_f32().unwrap();
let ay = self.y.to_f32().unwrap();
let bx = dest.x.to_f32().unwrap();
let by = dest.y.to_f32().unwrap();
for i in range_inclusive(Zero::zero(), n) {
let d = i.to_f32().unwrap() / n.to_f32().unwrap();
let x = ax + (bx - ax) * d;
let y = ay + (by - ay) * d;
let c1 = Coordinate::from_round(x + 0.000001, y + 0.000001);
let c2 = Coordinate::from_round(x - 0.000001, y - 0.000001);
f((c1, c2));
}
}
pub fn line_to(&self, dest : Coordinate<I>) -> Vec<Coordinate<I>>
where
for <'a> &'a I: Add<&'a I, Output = I>
{
let mut res = Vec::new();
self.for_each_in_line_to(dest, |c| {
res.push(c);
});
res
}
pub fn line_to_lossy(&self, dest : Coordinate<I>) -> Vec<Coordinate<I>>
where
for <'a> &'a I: Add<&'a I, Output = I>
{
let mut res = Vec::new();
self.for_each_in_line_to_lossy(dest, |c| {
res.push(c);
});
res
}
pub fn line_to_with_edge_detection(&self, dest : Coordinate<I>) -> Vec<(Coordinate<I>, Coordinate<I>)>
where
for <'a> &'a I: Add<&'a I, Output = I>
{
let mut res = Vec::new();
self.for_each_in_line_to_with_edge_detection(dest, |c| {
res.push(c);
});
res
}
pub fn z(&self) -> I
{
-self.x - self.y
}
pub fn direction_from_center_cw(&self) -> Option<Direction> {
let x = self.x;
let y = self.y;
let z = self.z();
let zero : I = num::FromPrimitive::from_i8(0).unwrap();
let xy = if z < zero { x >= y } else { x > y };
let yz = if x < zero { y >= z } else { y > z };
let zx = if y < zero { z >= x } else { z > x };
match (xy, yz, zx) {
(true, true, false) => Some(XZ),
(true, false, false) => Some(XY),
(true, false, true) => Some(ZY),
(false, false, true) => Some(ZX),
(false, true, true) => Some(YX),
(false, true, false) => Some(YZ),
(false, false, false) => None,
(true, true, true) => panic!("You broke math"),
}
}
pub fn directions_from_center(&self) -> Vec<Direction> {
let x = self.x;
let y = self.y;
let z = self.z();
let zero : I = num::FromPrimitive::from_i8(0).unwrap();
let mut dirs = Vec::with_capacity(2);
if x > zero && z < zero {
dirs.push(XZ)
}
if x > zero && y < zero {
dirs.push(XY)
}
if z > zero && y < zero {
dirs.push(ZY)
}
if z > zero && x < zero {
dirs.push(ZX)
}
if y > zero && z < zero {
dirs.push(YZ)
}
if y > zero && x < zero {
dirs.push(YX)
}
dirs
}
pub fn direction_from_center_ccw(&self) -> Option<Direction> {
let x = self.x;
let y = self.y;
let z = self.z();
let zero : I = num::FromPrimitive::from_i8(0).unwrap();
let xy = if z > zero { x >= y } else { x > y };
let yz = if x > zero { y >= z } else { y > z };
let zx = if y > zero { z >= x } else { z > x };
match (xy, yz, zx) {
(true, true, false) => Some(XZ),
(true, false, false) => Some(XY),
(true, false, true) => Some(ZY),
(false, false, true) => Some(ZX),
(false, true, true) => Some(YX),
(false, true, false) => Some(YZ),
(false, false, false) => None,
(true, true, true) => panic!("You broke math"),
}
}
pub fn directions_to(&self, coord : Coordinate<I>) -> Vec<Direction> {
(coord - *self).directions_from_center()
}
pub fn direction_to_cw(&self, coor : Coordinate<I>) -> Option<Direction> {
(coor - *self).direction_from_center_cw()
}
pub fn direction_to_ccw(&self, coor: Coordinate<I>) -> Option<Direction> {
(coor - *self).direction_from_center_ccw()
}
pub fn distance(&self, c : Coordinate<I>) -> I {
((self.x - c.x).abs() + (self.y - c.y).abs() + (self.z() - c.z()).abs())
/ num::FromPrimitive::from_i8(2).unwrap()
}
pub fn range(&self, r : I) -> Vec<Coordinate<I>>
where
for <'a> &'a I: Add<&'a I, Output = I>
{
let rc = (if r < Zero::zero() { I::one()-r } else { r }).to_usize().unwrap();
let mut res = Vec::with_capacity(3*(rc+rc*rc)+1);
self.for_each_in_range(r, |c| res.push(c));
res
}
pub fn for_each_in_range<F>(&self, r : I, mut f : F)
where
F : FnMut(Coordinate<I>),
for <'a> &'a I: Add<&'a I, Output = I> {
for x in range_inclusive(-r, r) {
for y in range_inclusive(max(-r, -x-r), min(r, -x+r)) {
f(Coordinate{
x: self.x + x,
y: self.y + y,
});
}
}
}
pub fn ring(&self, r : i32, s : Spin) -> Vec<Coordinate<I>> {
let mut res = Vec::with_capacity(if r == 0 { 1 } else if r < 0 { (r*-6) as usize } else { (r*6) as usize });
self.for_each_in_ring(r, s, |c| res.push(c));
res
}
pub fn for_each_in_ring<F>(&self, r : i32, s : Spin, mut f : F)
where F : FnMut(Coordinate<I>) {
if r == 0 {
f(*self);
return;
}
let (start_angle, step_angle, start_dir) = match s {
CW(d) => (RightBack, Right, d),
CCW(d) => (LeftBack, Left, d),
};
let mut cur_coord = *self + start_dir.to_coordinate().scale(
num::FromPrimitive::from_i32(r).unwrap()
);
let mut cur_dir = start_dir + start_angle;
for _ in 0..6 {
let cur_dir_coord = cur_dir.to_coordinate();
for _ in 0..r {
f(cur_coord);
cur_coord = cur_coord + cur_dir_coord;
}
cur_dir = cur_dir + step_angle;
}
}
}
impl<I : Integer> ToCoordinate<I> for Coordinate<I> {
fn to_coordinate(&self) -> Coordinate<I> {
*self
}
}
impl<I : Integer> ToCoordinate<I> for (I, I) {
fn to_coordinate(&self) -> Coordinate<I> {
let (x, y) = *self;
Coordinate::new(x, y)
}
}
impl<I : Integer, T: ToCoordinate<I>> Add<T> for Coordinate<I> {
type Output = Coordinate<I>;
fn add(self, c : T) -> Coordinate<I> {
let c = c.to_coordinate();
Coordinate {
x: self.x + c.x,
y: self.y + c.y,
}
}
}
impl<I : Integer, T: ToCoordinate<I>> Sub<T> for Coordinate<I> {
type Output = Coordinate<I>;
fn sub(self, c : T) -> Coordinate<I> {
let c = c.to_coordinate();
Coordinate {
x: self.x - c.x,
y: self.y - c.y,
}
}
}
impl<I : Integer> Neg for Coordinate<I>
{
type Output = Coordinate<I>;
fn neg(self) -> Coordinate<I> {
Coordinate { x: -self.x, y: -self.y }
}
}
impl<I : Integer> Position<I>
{
pub fn new(coord : Coordinate<I>, dir : Direction) -> Position<I> {
Position{ coord: coord, dir: dir }
}
}
impl<I : Integer> ToDirection for Position<I>
{
fn to_direction(&self) -> Direction {
self.dir
}
}
impl<I : Integer> ToCoordinate<I> for Position<I>
{
fn to_coordinate(&self) -> Coordinate<I> {
self.coord
}
}
impl<I : Integer> Add<Coordinate<I>> for Position<I> {
type Output = Position<I>;
fn add(self, c : Coordinate<I>) -> Position<I> {
let c = c.to_coordinate();
Position {
coord: self.coord + c,
dir: self.dir,
}
}
}
impl<I : Integer> Sub<Coordinate<I>> for Position<I>
{
type Output = Position<I>;
fn sub(self, c : Coordinate<I>) -> Position<I> {
let c = c.to_coordinate();
Position {
coord: self.coord - c,
dir: self.dir,
}
}
}
impl<I : Integer> Add<Angle> for Position<I>
{
type Output = Position<I>;
fn add(self, a : Angle) -> Position<I> {
Position {
coord: self.coord,
dir: self.dir + a,
}
}
}
impl Direction {
pub fn all() -> &'static [Direction; 6] {
&ALL_DIRECTIONS
}
pub fn arc(&self, steps : u8) -> Vec<Direction> {
match steps {
0 => vec!(*self),
1 => vec!(*self + Left, *self, *self + Right),
2 => vec!(*self + LeftBack, *self + Left, *self, *self + Right, *self + RightBack),
_ => vec!(*self + LeftBack, *self + Left, *self, *self + Right, *self + RightBack, *self + Back),
}
}
pub fn from_int<I : Integer>(i : I) -> Direction {
match i.mod_floor(&num::FromPrimitive::from_i8(6).unwrap()).to_u8().unwrap() {
0 => YZ,
1 => XZ,
2 => XY,
3 => ZY,
4 => ZX,
5 => YX,
_ => panic!()
}
}
pub fn to_int<I : Integer>(&self) -> I {
num::FromPrimitive::from_u8(*self as u8).unwrap()
}
}
impl ToDirection for Direction {
fn to_direction(&self) -> Direction {
*self
}
}
impl<T: ToDirection> Sub<T> for Direction {
type Output = Angle;
fn sub(self, c : T) -> Angle {
let c = c.to_direction();
Angle::from_int::<i8>(self.to_int::<i8>() - c.to_int::<i8>())
}
}
impl<I : Integer> ToCoordinate<I> for Direction {
fn to_coordinate(&self) -> Coordinate<I> {
let (x, y) = match *self {
YZ => (0, 1),
XZ => (1, 0),
XY => (1, -1),
ZY => (0, -1),
ZX => (-1, 0),
YX => (-1, 1),
};
Coordinate {
x: num::FromPrimitive::from_i8(x).unwrap(),
y: num::FromPrimitive::from_i8(y).unwrap(),
}
}
}
impl Neg for Direction {
type Output = Direction ;
fn neg(self) -> Direction {
Direction::from_int::<i8>(self.to_direction().to_int::<i8>() + 3)
}
}
impl Angle {
pub fn all() -> &'static [Angle; 6] {
&ALL_ANGLES
}
pub fn from_int<I : Integer>(i : I) -> Angle {
match i.mod_floor(&num::FromPrimitive::from_i8(6).unwrap()).to_u8().unwrap() {
0 => Forward,
1 => Right,
2 => RightBack,
3 => Back,
4 => LeftBack,
5 => Left,
_ => panic!()
}
}
pub fn to_int<I : Integer>(&self) -> I {
num::FromPrimitive::from_u8(*self as u8).unwrap()
}
}
impl Add<Angle> for Direction {
type Output = Direction;
fn add(self, a : Angle) -> Direction {
Direction::from_int(self.to_int::<i8>() + a.to_int::<i8>())
}
}