bestagon 0.6.0

An engine for discrete stuff in hexagonal grids
Documentation
use crate::{
  distance::Distance,
  edge::{EdgeAx, FEdgeAx},
  hex::{FHexAx, HexAx, HexCb},
  math::Vec2,
  node::*,
  round::Roundable,
};

/// Implementations of this trait should return the cartesian coordinates
/// of the center of the primitive.
/// Note that this trait assumes hex cells of size 1 (radius of outer circle)
pub trait ToCartesian<T> {
  fn to_cartesian(&self) -> Vec2<T>;
}

impl ToCartesian<f32> for HexAx {
  fn to_cartesian(&self) -> Vec2<f32> {
    FHexAx::from(self).to_cartesian()
  }
}

impl ToCartesian<f32> for FHexAx {
  fn to_cartesian(&self) -> Vec2<f32> {
    Vec2::new(
      3. / 2. * self.q,
      f32::sqrt(3.) / 2. * self.q + f32::sqrt(3.) * self.r,
    )
  }
}

/// Implementations of this trait should return the nearest primitive and
/// the Euclidean distance to it.
/// Note that this trait assumes hex cells of size 1 (radius of outer circle)
pub trait FromCartesian<T>: Sized {
  fn from_cartesian(vec: &Vec2<T>) -> (Self, T);
}

impl FromCartesian<f32> for HexAx {
  fn from_cartesian(vec: &Vec2<f32>) -> (Self, f32) {
    let floating = FHexAx::from_cartesian(vec).0;
    let nearest = floating.round();
    let nearest_cart = nearest.to_cartesian();
    (nearest, vec.dist(&nearest_cart))
  }
}

impl FromCartesian<f32> for FHexAx {
  fn from_cartesian(vec: &Vec2<f32>) -> (Self, f32) {
    let floating = FHexAx::new(
      vec.x * 2. / 3.,
      vec.y * f32::sqrt(3.) / 3. - vec.x * (1. / 3.),
    );
    (floating, 0.0)
  }
}

// TODO: Implementations like this (via Into / From) can be automated?
impl FromCartesian<f32> for HexCb {
  fn from_cartesian(vec: &Vec2<f32>) -> (Self, f32) {
    let (nearest_ax, distance) = HexAx::from_cartesian(vec);
    (nearest_ax.into(), distance)
  }
}

impl ToCartesian<f32> for EdgeAx {
  fn to_cartesian(&self) -> Vec2<f32> {
    // this works because EdgeAx's coordinates are the sums of their
    // neighbouring hexagon's coordinates.
    let hex = FHexAx::new((self.q as f32) / 2., (self.r as f32) / 2.);
    hex.to_cartesian()
  }
}
impl ToCartesian<f32> for FEdgeAx {
  fn to_cartesian(&self) -> Vec2<f32> {
    // this works because EdgeAx's coordinates are the sums of their
    // neighbouring hexagon's coordinates.
    let hex = FHexAx::new(self.q / 2., self.r / 2.);
    hex.to_cartesian()
  }
}

// TODO: Test especially this part
impl FromCartesian<f32> for EdgeAx {
  fn from_cartesian(vec: &Vec2<f32>) -> (Self, f32) {
    let floating = FEdgeAx::from_cartesian(vec).0;
    let nearest = floating.round();
    let nearest_cart = nearest.to_cartesian();
    (nearest, vec.dist(&nearest_cart))
  }
}

impl FromCartesian<f32> for FEdgeAx {
  fn from_cartesian(vec: &Vec2<f32>) -> (Self, f32) {
    let (nearest_2ax, _) = FHexAx::from_cartesian(vec);
    (FEdgeAx::new(2.0 * nearest_2ax.q, 2.0 * nearest_2ax.r), 0.0)
  }
}

impl ToCartesian<f32> for NodeAx {
  fn to_cartesian(&self) -> Vec2<f32> {
    FNodeAx::from(self).to_cartesian()
  }
}

impl ToCartesian<f32> for FNodeAx {
  fn to_cartesian(&self) -> Vec2<f32> {
    let hex = FHexAx::new(self.q / 3., self.r / 3.);
    hex.to_cartesian()
  }
}

impl FromCartesian<f32> for NodeAx {
  fn from_cartesian(vec: &Vec2<f32>) -> (Self, f32) {
    let floating = FNodeAx::from_cartesian(vec).0;
    let nearest = floating.round();
    let nearest_cart = nearest.to_cartesian();
    (nearest, vec.dist(&nearest_cart))
  }
}

impl FromCartesian<f32> for FNodeAx {
  fn from_cartesian(vec: &Vec2<f32>) -> (Self, f32) {
    let (nearest_3ax, _) = FHexAx::from_cartesian(vec);
    (FNodeAx::new(3.0 * nearest_3ax.q, 3.0 * nearest_3ax.r), 0.0)
  }
}

#[cfg(test)]
mod test {
  use crate::{
    edge::{Edge, EdgeAx},
    hex::Hex,
    hex_range::HexRange,
    math::AverageExt,
    neighbours::Neighbours,
  };

  use super::*;

  #[test]
  fn neighbours_should_be_distance() {
    let cell = HexAx::new(0, 0);
    let cart = cell.to_cartesian();
    let neighbours = cell.neighbours();
    for n in neighbours {
      // comparing floats for equality surprisingly works here
      // but should maybe be 'eps'ed.
      assert_eq!(n.to_cartesian().dist(&cart), f32::sqrt(3.0));
    }
  }

  #[test]
  fn edge_cartesian_should_be_in_between_neighbour_cells() {
    for edge in HexAx::new(0, 0).edges::<EdgeAx>().iter() {
      let edge_cart = edge.to_cartesian();
      let neighbours: Vec<HexAx> = edge.hexes().into_iter().collect();
      assert_eq!(2, neighbours.len());
      assert_eq!(
        edge_cart,
        neighbours.iter().map(|n| n.to_cartesian()).average(),
      );
    }
  }

  #[test]
  fn node_cartesian_should_be_in_between_neighbour_cells() {
    for node in HexAx::new(0, 0).nodes::<NodeAx>().iter() {
      let node_cart = node.to_cartesian();
      let neighbours: Vec<HexAx> = node.hexes().into_iter().collect();
      assert_eq!(3, neighbours.len());
      assert_eq!(
        node_cart,
        neighbours.iter().map(|n| n.to_cartesian()).average(),
      );
    }
  }

  #[test]
  fn node_from_cartesian_between_neighbour_hexes() {
    for hex in HexRange::new((-3, 3), (-3, 3), (-3, 3)).compute::<HexAx>() {
      for node in hex.nodes::<NodeAx>().iter() {
        let should_be_node_point: Vec2<f32> = node
          .hexes()
          .iter()
          .map(|h: &HexAx| h.to_cartesian())
          .average();

        let (should_be_node, _dist) = NodeAx::from_cartesian(&should_be_node_point);
        assert_eq!(should_be_node, *node);
      }
    }
  }
}